WPF支持任意快捷键+鼠标组合的绑定类

    public interface IInputSignal
    {
        bool IsMatch(InputEventArgs args);
    }

    public class KeyDownSignal : KeySignal
    {
        public KeyDownSignal(Key key) : base(key)
        {
        }

        public override bool IsMatch(InputEventArgs args)
        {
            return Keyboard.IsKeyDown(Key);
        }
    }

    public class KeySignal : IInputSignal
    {
        public Key Key { get; set; }

        public KeySignal(Key key)
        {
            Key = key;
        }

        public virtual bool IsMatch(InputEventArgs args)
        {
            return args is KeyEventArgs e && e.Key == Key;
        }
    }

    public class KeyRepeatSignal : KeySignal
    {
        public KeyRepeatSignal(Key key) : base(key)
        {
        }

        public override bool IsMatch(InputEventArgs args)
        {
            return args is KeyEventArgs e && Key == e.Key && e.IsRepeat;
        }
    }

    public class MouseSignal : IInputSignal
    {
        public MouseButton MouseButton { get; set; }
        public MouseButtonState MouseButtonState { get; set; }
        public MouseSignal(MouseButton mouseButton, MouseButtonState state)
        {
            MouseButton = mouseButton;
            MouseButtonState = state;
        }

        public virtual bool IsMatch(InputEventArgs args)
        {
            MouseButtonEventArgs e = (MouseButtonEventArgs)args;
            return e != null && e.ChangedButton == MouseButton && e.ButtonState == MouseButtonState;
        }
    }

    public class MouseWheelSignal : IInputSignal
    {
        public bool IsMatch(InputEventArgs args)
        {
            MouseWheelEventArgs e = (MouseWheelEventArgs)args;
            return e != null;
        }
    }
    public class CommandMethod
    {
        public Action<CommandMethodEventArgs?>? Execute { get; set; }
        public Predicate<CommandMethodEventArgs?>? CanExecute { get; set; }
        public CommandMethod(Action<CommandMethodEventArgs?>? execute, Predicate<CommandMethodEventArgs?>? canExecute = null)
        {
            Execute = execute;
            CanExecute = canExecute;
        }
    }
    public class CommandMethodEventArgs : EventArgs
    {
        public bool Handled { get; set; }
        public object Data { get; set; }
        public InputCommand Command { get; set; }
        public CommandMethodEventArgs(object data, InputCommand command)
        {
            Data = data;
            Command = command;
        }
    }

    public enum MatchModel { Step, All }
    public class InputCommand : ICommand
    {
        int _index = 0;
        DateTime? _dateTime;

        public List<CommandMethod> CommandMethods { get; set; }
        public List<IInputSignal> InputSignals { get; set; }
        public string Name { get; set; }
        /// <summary>
        /// Step按顺序按,All所有信号同时存在
        /// </summary>
        public MatchModel MatchModel { get; set; }
        public Window Owner { get; set; }
        public event EventHandler? CanExecuteChanged;

        public InputCommand(string name, IEnumerable<IInputSignal> inputSignals, MatchModel matchModel, Window owner)
            : this(name, inputSignals, matchModel, owner, null)
        {
        }

        public InputCommand(string name, IEnumerable<IInputSignal> inputSignals, MatchModel matchModel, Window owner, IEnumerable<CommandMethod>? commandMethods)
        {
            this.Name = name;
            MatchModel = matchModel;
            InputSignals = new List<IInputSignal>();
            if (inputSignals != null)
                InputSignals.AddRange(inputSignals);
            CommandMethods = new List<CommandMethod>();
            if (commandMethods != null)
                CommandMethods.AddRange(commandMethods);

            Owner = owner;
            if(Owner != null)
                Owner.CommandBindings.Add(new CommandBinding(this));
        }

        /// <summary>
        /// MatchModel.All时args可以为null
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public virtual bool IsMatch(InputEventArgs args)
        {
            if (InputSignals == null || InputSignals.Count <= 0) return false;
            if (MatchModel == MatchModel.Step)
            {
                if (InputSignals[_index].IsMatch(args))
                {
                    if (_dateTime == null)
                    {
                        _dateTime = DateTime.Now;
                        _index++;
                    }
                    else if ((DateTime.Now - _dateTime) <= TimeSpan.FromMilliseconds(200))
                    {
                        _index++;
                        _dateTime = DateTime.Now;
                    }
                    else
                    {
                        _index = 0;
                        _dateTime = null;
                        return false;
                    }
                    if (_index >= InputSignals.Count)
                    {
                        _index = 0;
                        _dateTime = null;
                        return true;
                    }
                }
            }
            else
            {
                foreach (var k in InputSignals)
                    if (!k.IsMatch(null))
                        return false;
                return true;
            }
            return false;
        }

        bool ICommand.CanExecute(object? parameter)
        {
            CommandMethodEventArgs arg = new CommandMethodEventArgs(parameter, this);
            return CanExecute(arg);
        }

        void ICommand.Execute(object? parameter)
        {
            CommandMethodEventArgs arg = new CommandMethodEventArgs(parameter, this);
            Execute(arg);
        }
        public bool CanExecute(CommandMethodEventArgs arg)
        {
            foreach (var method in CommandMethods)
            {
                if (method.CanExecute != null)
                {
                    bool can = method.CanExecute.Invoke(arg);
                    if (arg.Handled) return can;
                }
            }
            return true;
        }
        public void Execute(CommandMethodEventArgs arg)
        {
            foreach (var method in CommandMethods)
            {
                method.Execute?.Invoke(arg);
                if (arg.Handled) return;
            }
        }

        public void RaiseCmd()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
        public static void TryExecute(Window window, InputEventArgs e)
        {
            foreach (CommandBinding cmd in window.CommandBindings)
            {
                if (cmd.Command is InputCommand command)
                {
                    if (command.IsMatch(e))
                    {
                        CommandMethodEventArgs arg = new CommandMethodEventArgs(e, command);
                        command.Execute(arg);
                        if (arg.Handled) break;
                    }
                }
            }
        }
        public static void RegistPreview(Window window)
        {
            RegistPreviewKeyDown(window);
            RegistPreviewKeyUp(window);
            RegistPreviewMouseDown(window);
            RegistPreviewKeyUp(window);
        }
        public static void RegistPreviewKeyDown(Window window)
        {
            window.PreviewKeyDown += (s, e) => TryExecute(window, e);
        }
        public static void RegistPreviewKeyUp(Window window)
        {
            window.PreviewKeyUp += (s, e) => TryExecute(window, e);
        }
        public static void RegistPreviewMouseDown(Window window)
        {
            window.PreviewMouseDown += (s, e) => TryExecute(window, e);
        }
        public static void RegistPreviewMouseUp(Window window)
        {
            window.PreviewMouseUp += (s, e) => TryExecute(window, e);
        }
        public static void RegistPreviewMouseWheel(Window window)
        {
            window.PreviewMouseWheel += (s, e) => TryExecute(window, e);
        }
    }
View Code

使用方式:

    public class KeyBindingVM : ITemplateView
    {
        public ObservableCollection<InputCommand> InputCommands { get; set; }
        public string Title { get; set; } = "按键设置";
        public List<IInputSignal> OldKeys { get; set; }
        public SimpleCommand<KeyEventArgs> AddKeyCmd { get; set; }
        public SimpleCommand<RoutedEventArgs> ClearKeyCmd { get; set; }
        public SimpleCommand SaveCmd { get; set; }

        public KeyBindingVM()
        {
            OldKeys = new List<IInputSignal>();
            AddKeyCmd = new SimpleCommand<KeyEventArgs>(AddKey);
            ClearKeyCmd = new SimpleCommand<RoutedEventArgs>(ClearKey);
            SaveCmd = new SimpleCommand(() => { });
            InputCommands = new ObservableCollection<InputCommand>
            {
                new InputCommand("背包", new KeyDownSignal[]{ new KeyDownSignal(Key.B) }, MatchModel.All, App.Current.MainWindow, new List<CommandMethod>{ new CommandMethod(Alert) }),
                new InputCommand("技能", new KeyDownSignal[]{ new KeyDownSignal(Key.K) }, MatchModel.All, App.Current.MainWindow, new List<CommandMethod>{ new CommandMethod(Alert) }),
                new InputCommand("任务", new KeyDownSignal[]{ new KeyDownSignal(Key.O) }, MatchModel.All, App.Current.MainWindow, new List<CommandMethod>{ new CommandMethod(Alert) }),
                new InputCommand("地图", new KeyDownSignal[]{ new KeyDownSignal(Key.M) }, MatchModel.All, App.Current.MainWindow, new List<CommandMethod>{ new CommandMethod(Alert) }),
            };
        }

        void Alert(CommandMethodEventArgs? e)
        {
            MessageBox.Show(e.Command.Name);
        }

        private void ClearKey(RoutedEventArgs e)
        {
            var inputcmd = (e.OriginalSource as FrameworkElement).DataContext as InputCommand;
            OldKeys.Clear();
            OldKeys.AddRange(inputcmd.InputSignals);
            inputcmd.InputSignals.Clear();
        }

        private void AddKey(KeyEventArgs e)
        {
            e.Handled = true;
            if (e.IsRepeat) return;
            var control = (e.OriginalSource as TextBox);
            var inputcmd = control.DataContext as InputCommand;
            if (inputcmd.InputSignals.Exists(f => (f as KeySignal).Key == e.Key)) return;
            inputcmd.InputSignals.Add(new KeyDownSignal(e.Key));
            control.SetBinding(TextBox.TextProperty, new Binding { Source = inputcmd, Path = new PropertyPath(nameof(inputcmd.InputSignals)), Converter = new Converters.KeyStringConverter() });
        }
    }

    public class KeyStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return value;
            var keys = value as List<IInputSignal>;
            if (keys == null || keys.Count <= 0) return null;
            return string.Join("+", keys.Select(f => (f as KeySignal).Key));
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
View Code
<StackPanel>
                <ItemsControl ItemsSource="{Binding InputCommands}" VerticalAlignment="Top">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid Columns="2"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate DataType="{x:Type input:InputCommand}">
                            <UniformGrid Columns="2">
                                <TextBlock Text="{Binding Name}"/>
                                <TextBox Text="{Binding Path=InputSignals, Converter={StaticResource KeyStringConverter}}">
                                    <i:Interaction.Triggers>
                                        <i:EventTrigger EventName="PreviewKeyDown">
                                            <i:InvokeCommandAction Command="{Binding DataContext.AddKeyCmd, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ItemsControl}}}" PassEventArgsToCommand="True"/>
                                        </i:EventTrigger>
                                        <i:EventTrigger EventName="PreviewMouseLeftButtonDown">
                                            <i:InvokeCommandAction Command="{Binding DataContext.ClearKeyCmd, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ItemsControl}}}" PassEventArgsToCommand="True"/>
                                        </i:EventTrigger>
                                    </i:Interaction.Triggers>
                                </TextBox>
                            </UniformGrid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
                <Button Content="保存" Command="{Binding SaveCmd}"/>
            </StackPanel>
View Code

 

 

posted @ 2024-06-20 16:34  HotSky  阅读(6)  评论(0编辑  收藏  举报