Crux Gaming

General Details

A program designed to give an interface driven way to automate repetitive or visually cued events.

The user is able to set up keyboard and mouse button combinations that will cause other events to trigger, conditionally or not. An example use case, as demoed in the series of screenshots, would be when pressing (Ctrl+S) while in photoshop, check if the canvas is showing at a specific screen position, and if so, crop the image and then export as a JPEG.

Technical Details

- Written in C# using WinForms.
- Separation of the Presentation code from the Logic/Business layer.


                    using Gma.System.MouseKeyHook;
                    using Nahk.Utils;
                    using Nahk.ViewModels;
                    using NahkLogic.Business;
                    using NahkLogic.Common;

                    namespace Nahk.Forms;

                    /// 
                    /// PauseDialog
                    /// Shows a dialog window with all needed options for a user to set up a pause event.
                    /// 
                    public partial class PauseDialog : Form
                    {
                        public required ProfileViewModel ProfileVM { get; set; }
                        public PauseViewModel PauseVM { get; set; } = new();
                        public PixelCheck PixelCheck { get; set; } = new();
                        public bool IsEditMode { get; set; }
                        public int SelectedIndex { get; set; } = -1;

                        private bool debugPixelInfo = false;


                        public PauseDialog()
                        {
                            InitializeComponent();
                        }

                        private void PauseDialog_Load(object sender, EventArgs e)
                        {
                            Text = IsEditMode ? "Editing Pause Condition" : "New Pause Condition";
                            textBoxWinName.Text = ProfileVM.Model.WindowTitle;
                            SetBindings();
                            SetDataSources();

                            if (comboBoxPixelChecks.Items.Count > 0)
                            {
                                comboBoxPixelChecks.SelectedIndex = 0;
                                PixelCheck = PauseVM.Model.PixelChecks[comboBoxPixelChecks.SelectedIndex];
                                RefreshPixelInfo();
                            }
                        }

                        private void PauseDialog_FormClosed(object sender, FormClosedEventArgs e)
                        {
                            Unsubscribe();
                        }

                        public void Subscribe()
                        {
                            FormMain.Hook.MouseDownExt += MouseHook;
                        }

                        public void Unsubscribe()
                        {
                            FormMain.Hook.MouseDownExt -= MouseHook;
                        }

                        private void SetBindings()
                        {
                            textBoxName.DataBindings.Add("Text", this, "PauseVM.Model.Name", true, DataSourceUpdateMode.OnPropertyChanged);
                            checkBoxEnabled.DataBindings.Add("Checked", this, "PauseVM.Model.Enabled", true, DataSourceUpdateMode.OnPropertyChanged);

                            textBoxPixelCheckName.DataBindings.Add("Text", this, "PixelCheck.Name", true, DataSourceUpdateMode.OnPropertyChanged);
                            textBoxX.DataBindings.Add("Text", this, "PixelCheck.X", true, DataSourceUpdateMode.OnPropertyChanged);
                            textBoxY.DataBindings.Add("Text", this, "PixelCheck.Y", true, DataSourceUpdateMode.OnPropertyChanged);
                            pictureBoxColor.DataBindings.Add("BackColor", this, "PixelCheck.Color", true, DataSourceUpdateMode.OnPropertyChanged);
                            checkBoxOnFound.DataBindings.Add("Checked", this, "PixelCheck.OnFound", true, DataSourceUpdateMode.OnPropertyChanged);
                        }

                        private void SetDataSources()
                        {
                            comboBoxPixelChecks.DataSource = null;
                            comboBoxPixelChecks.DataSource = PauseVM.Model.PixelChecks;
                            comboBoxPixelChecks.DisplayMember = "Name";
                            groupBoxPixelCheck.Enabled = comboBoxPixelChecks.Items.Count > 0;
                        }

                        private void buttonOk_Click(object sender, EventArgs e)
                        {
                            if (ProfileValidation.ValidatePause(ProfileVM.Model, PauseVM.Model, IsEditMode))
                            {
                                DialogResult = DialogResult.OK;
                            }
                            errorProvider.SetError(buttonOk, ProfileValidation.ErrorMessage);
                        }

                        private void buttonDelete_Click(object sender, EventArgs e)
                        {
                            var result = MessageBox.Show("Delete this Pause Condition?", "Delete Pause Condition", MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                            if (result != DialogResult.Yes || SelectedIndex == -1) return;

                            ProfileVM.Model.PauseList.RemoveAt(SelectedIndex);
                            DialogResult = DialogResult.Cancel;
                        }

                        private void buttonPixelInfo_Click(object sender, EventArgs e)
                        {
                            var hWnd = Nhk.GetWindowHandle(ProfileVM.Model.WindowTitle);
                            if (hWnd == IntPtr.Zero)
                            {
                                textBoxWinName.Text = "Window not found...";
                                return;
                            }
                            Subscribe();
                            debugPixelInfo = true;
                            Task.Run(() => GetPixelInfo(hWnd));
                        }

                        private async Task GetPixelInfo(IntPtr hWnd)
                        {
                            while (debugPixelInfo)
                            {
                                Invoke((MethodInvoker)Inv);
                                await Task.Delay(200);
                                continue;

                                void Inv()
                                {
                                    var screenPoint = new Point(Cursor.Position.X, Cursor.Position.Y);
                                    var wndPoint = Nhk.ScreenToWindowPoint(hWnd, screenPoint);
                                    Nhk.CaptureWindowBmp(hWnd);
                                    var color = Nhk.GetBmpPixel(wndPoint);
                                    textBoxX.Text = $"{wndPoint.X}";
                                    textBoxY.Text = $"{wndPoint.Y}";
                                    if (color != null)
                                    {
                                        pictureBoxColor.BackColor = (Color)color;
                                    }
                                }
                            }
                        }

                        private void MouseHook(object? sender, MouseEventExtArgs e)
                        {
                            if (e.Button == MouseButtons.Right)
                            {
                                e.Handled = true;
                                debugPixelInfo = false;
                            }
                        }

                        private void textBoxName_Validating(object sender, System.ComponentModel.CancelEventArgs e)
                        {
                            if (!ProfileValidation.ValidatePauseName(ProfileVM.Model, textBoxName.Text, IsEditMode))
                            {
                                e.Cancel = true;
                                textBoxName.SelectAll();
                            }
                            errorProvider.SetError(buttonOk, ProfileValidation.ErrorMessage);
                        }

                        private void comboBoxPixelChecks_SelectionChangeCommitted(object sender, EventArgs e)
                        {
                            if (comboBoxPixelChecks.SelectedIndex < 0) return;
                            PixelCheck = PauseVM.Model.PixelChecks[comboBoxPixelChecks.SelectedIndex];
                            RefreshPixelInfo();
                        }

                        private void buttonAddPixelCheck_Click(object sender, EventArgs e)
                        {
                            PixelCheck = new PixelCheck();
                            PauseVM.Model.PixelChecks.Add(PixelCheck);
                            SetDataSources();
                            RefreshPixelInfo();
                        }

                        private void buttonDeletePixelCheck_Click(object sender, EventArgs e)
                        {
                            PauseVM.Model.PixelChecks.Remove(PixelCheck);
                            SetDataSources();

                            if (comboBoxPixelChecks.Items.Count <= 0) return;

                            comboBoxPixelChecks.SelectedIndex = comboBoxPixelChecks.Items.Count - 1;
                            PixelCheck = PauseVM.Model.PixelChecks[comboBoxPixelChecks.SelectedIndex];
                            RefreshPixelInfo();
                        }

                        private void RefreshPixelInfo()
                        {
                            textBoxPixelCheckName.BindingContext = new BindingContext();
                            textBoxX.BindingContext = new BindingContext();
                            textBoxY.BindingContext = new BindingContext();
                            pictureBoxColor.BindingContext = new BindingContext();
                            checkBoxOnFound.BindingContext = new BindingContext();
                        }

                        private void PauseDialog_FormClosing(object sender, FormClosingEventArgs e)
                        {
                            debugPixelInfo = false;
                        }
                    }

                    

                    using System.ComponentModel;
                    using System.Runtime.CompilerServices;
                    using NahkLogic.Common;

                    namespace Nahk.ViewModels;

                    /// 
                    /// Base class for all ViewModel classes
                    /// 
                    public class ViewModelBase : INotifyPropertyChanged where T : new()
                    {
                        public event PropertyChangedEventHandler? PropertyChanged;

                        private string xml = string.Empty;
                        private T model = new();
                        public T Model
                        {
                            get => model;
                            set
                            {
                                if (value == null) return;

                                xml = value.XmlSerializeToString();
                                model = xml.XmlDeserializeFromString() ?? new T();
                                OnPropertyChanged();
                            }
                        }

                        public void RefreshDisplay() => OnPropertyChanged(nameof(Model));

                        public bool HasChangedSinceLoad() => model != null && model.XmlSerializeToString() != xml;

                        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
                        {
                            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                        }
                    }
                

                using System.Xml.Serialization;
                using Microsoft.VisualBasic.FileIO;
                using NahkLogic.Common;

                namespace NahkLogic.Data;

                /// 
                /// ProfileRepository
                /// Provides a singular point for all verification of profile data.
                /// 
                internal class ProfileRepository
                {
                    public static List GetProfileNames(bool lower = false)
                    {
                        if (!Directory.Exists(Config.SavedProfileDirectory))
                        {
                            Directory.CreateDirectory(Config.SavedProfileDirectory);
                        }

                        List files;
                        if (lower)
                        {
                            files = Directory.GetFiles(Config.SavedProfileDirectory, "*" + Config.FileExtension).Select(f => f.Split('\\').Last().ToLower()).ToList();
                        }

                        files = Directory.GetFiles(Config.SavedProfileDirectory, "*" + Config.FileExtension).Select(f => f.Split('\\').Last()).ToList();
                        files.Remove(Config.SettingsFileName + Config.FileExtension);
                        return files;
                    }

                    public static int SaveProfile(Profile profile)
                    {
                        try
                        {
                            if (!Directory.Exists(Config.SavedProfileDirectory))
                            {
                                Directory.CreateDirectory(Config.SavedProfileDirectory);
                            }
                            var serializer = new XmlSerializer(typeof(Profile));
                            using StreamWriter writer = new(Config.SavedProfileDirectory + profile.ProfileName + Config.FileExtension);
                            serializer.Serialize(writer, profile);
                        }
                        catch (Exception e)
                        {
                            return -1;
                        }

                        return 0;
                    }

                    public static Profile? LoadProfile(string name)
                    {
                        var serializer = new XmlSerializer(typeof(Profile));
                        using StreamReader reader = new(Config.SavedProfileDirectory + name);
                        return serializer.Deserialize(reader) as Profile ?? new Profile();
                    }

                    public static void DeleteProfile(Profile profile)
                    {
                        FileSystem.DeleteFile(Config.SavedProfileDirectory + profile.ProfileName + Config.FileExtension, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
                    }
                }
                
Copyright © Nicholus Huber / Crux Gaming