أحداث لوحة المفاتيح في WPF MVVM التطبيق ؟

StackOverflow https://stackoverflow.com/questions/612966

  •  03-07-2019
  •  | 
  •  

سؤال

كيف يمكنني التعامل مع لوحة المفاتيح.الحدث KeyDown دون استخدام رمز وراء ؟ نحن نحاول استخدام نمط MVVM و تجنب كتابة معالج حدث في التعليمات البرمجية-خلف الملف.

هل كانت مفيدة؟

المحلول

وقليلا في وقت متأخر، ولكن هنا يذهب.

وفريق WPF مايكروسوفت صدر مؤخرا نسخة مبكرة من هم WPF MVVM أدوات . في ذلك، وسوف تجد فئة تسمى CommandReference التي يمكن التعامل مع أشياء مثل كيبيندينغس. انظر في قالب WPF MVVM لمعرفة كيف يعمل.

نصائح أخرى

لوضع الإجابة المحدثة، على .NET Framework 4.0 يتيح لك القيام بذلك بشكل جيد عن طريق السماح لك ربط قيادة رابط المفتاح إلى أمر في viewmodel.

وهكذا ... إذا كنت تريد الاستماع للمفتاح Enter، وكنت تفعل شيئا مثل هذا:

<TextBox AcceptsReturn="False">
    <TextBox.InputBindings>
        <KeyBinding 
            Key="Enter" 
            Command="{Binding SearchCommand}" 
            CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

WOW - هناك مثل ألف إجابات وهنا انا ذاهب لإضافة واحد آخر ..

والشيء الواضح حقا في نوع 'لماذا-didn't-I-يدركون-هذا الجبين صفعة "للطريقة هو أن التعليمات البرمجية الخلفية وViewModel الجلوس في غرفة واحدة حتى، إلى الكلام، لذلك ليس هناك من سبب لماذا لا يسمح لإجراء محادثة.

إذا كنت تفكر في ذلك، وبالفعل يقترن XAML ارتباطا وثيقا API في ViewModel، لذلك قد اذهبوا وكذلك جعل تبعية عليه من التعليمات البرمجية خلف.

والقواعد واضحة أخرى على طاعة أو تجاهل لا يزال ساريا (واجهات والشيكات فارغة <- خاصة إذا كنت تستخدم مزيج ...)

وأنا دائما خاصية في التعليمات البرمجية وراء مثل هذا:

private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }

وهذا هو رمز العميل. الاختيار باطل هو لمساعدة السيطرة استضافة كما هو الحال في مزيج.

void someEventHandler(object sender, KeyDownEventArgs e)
{
    if (ViewModel == null) return;
    /* ... */
    ViewModel.HandleKeyDown(e);
}

والتعامل مع هذا الحدث الخاص بك في التعليمات البرمجية خلف وكأنك تريد أن (الأحداث UI هي ذات واجهة مستخدم مركزية بحيث انه موافق) وبعد ذلك يكون أسلوب على ViewModelClass التي يمكن أن تستجيب لهذا الحدث. لا تزال تفصل الشواغل.

ViewModelClass
{
    public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}

وجميع هذه الخصائص الأخرى المرفقة والفودو هو بارد جدا والتقنيات مفيدة حقا لبعض الأشياء الأخرى، ولكن هنا قد تفلت من شيء أكثر بساطة ...

أنا القيام بذلك باستخدام ملحق السلوك مع 3 التبعية خصائص.هو واحد الأوامر لتنفيذ واحد هو المعلمة بالمرور إلى الأمر الآخر هو المفتاح الذي سوف يؤدي الأمر إلى تنفيذ.هنا كود:

public static class CreateKeyDownCommandBinding
{
    /// <summary>
    /// Command to execute.
    /// </summary>
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command",
        typeof(CommandModelBase),
        typeof(CreateKeyDownCommandBinding),
        new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated)));

    /// <summary>
    /// Parameter to be passed to the command.
    /// </summary>
    public static readonly DependencyProperty ParameterProperty =
        DependencyProperty.RegisterAttached("Parameter",
        typeof(object),
        typeof(CreateKeyDownCommandBinding),
        new PropertyMetadata(new PropertyChangedCallback(OnParameterInvalidated)));

    /// <summary>
    /// The key to be used as a trigger to execute the command.
    /// </summary>
    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.RegisterAttached("Key",
        typeof(Key),
        typeof(CreateKeyDownCommandBinding));

    /// <summary>
    /// Get the command to execute.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static CommandModelBase GetCommand(DependencyObject sender)
    {
        return (CommandModelBase)sender.GetValue(CommandProperty);
    }

    /// <summary>
    /// Set the command to execute.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="command"></param>
    public static void SetCommand(DependencyObject sender, CommandModelBase command)
    {
        sender.SetValue(CommandProperty, command);
    }

    /// <summary>
    /// Get the parameter to pass to the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static object GetParameter(DependencyObject sender)
    {
        return sender.GetValue(ParameterProperty);
    }

    /// <summary>
    /// Set the parameter to pass to the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="parameter"></param>
    public static void SetParameter(DependencyObject sender, object parameter)
    {
        sender.SetValue(ParameterProperty, parameter);
    }

    /// <summary>
    /// Get the key to trigger the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <returns></returns>
    public static Key GetKey(DependencyObject sender)
    {
        return (Key)sender.GetValue(KeyProperty);
    }

    /// <summary>
    /// Set the key which triggers the command.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="key"></param>
    public static void SetKey(DependencyObject sender, Key key)
    {
        sender.SetValue(KeyProperty, key);
    }

    /// <summary>
    /// When the command property is being set attach a listener for the
    /// key down event.  When the command is being unset (when the
    /// UIElement is unloaded for instance) remove the listener.
    /// </summary>
    /// <param name="dependencyObject"></param>
    /// <param name="e"></param>
    static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)dependencyObject;
        if (e.OldValue == null && e.NewValue != null)
        {
            element.AddHandler(UIElement.KeyDownEvent,
                new KeyEventHandler(OnKeyDown), true);
        }

        if (e.OldValue != null && e.NewValue == null)
        {
            element.RemoveHandler(UIElement.KeyDownEvent,
                new KeyEventHandler(OnKeyDown));
        }
    }

    /// <summary>
    /// When the parameter property is set update the command binding to
    /// include it.
    /// </summary>
    /// <param name="dependencyObject"></param>
    /// <param name="e"></param>
    static void OnParameterInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = (UIElement)dependencyObject;
        element.CommandBindings.Clear();

        // Setup the binding
        CommandModelBase commandModel = e.NewValue as CommandModelBase;
        if (commandModel != null)
        {
            element.CommandBindings.Add(new CommandBinding(commandModel.Command,
            commandModel.OnExecute, commandModel.OnQueryEnabled));
        }
    }

    /// <summary>
    /// When the trigger key is pressed on the element, check whether
    /// the command should execute and then execute it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void OnKeyDown(object sender, KeyEventArgs e)
    {
        UIElement element = sender as UIElement;
        Key triggerKey = (Key)element.GetValue(KeyProperty);

        if (e.Key != triggerKey)
        {
            return;
        }

        CommandModelBase cmdModel = (CommandModelBase)element.GetValue(CommandProperty);
        object parameter = element.GetValue(ParameterProperty);
        if (cmdModel.CanExecute(parameter))
        {
            cmdModel.Execute(parameter);
        }
        e.Handled = true;
    }
}

لاستخدام هذا من xaml يمكنك أن تفعل شيئا مثل هذا:

<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
    <framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>

تحرير: CommandModelBase قاعدة الطبقة يمكنني استخدام لجميع الأوامر.أنها تقوم على CommandModel فئة من دان Crevier المادة على MVVM (هنا).هنا مصدر نسخة معدلة قليلا أنا استخدم مع CreateKeyDownCommandBinding:

public abstract class CommandModelBase : ICommand
    {
        RoutedCommand routedCommand_;

        /// <summary>
        /// Expose a command that can be bound to from XAML.
        /// </summary>
        public RoutedCommand Command
        {
            get { return routedCommand_; }
        }

        /// <summary>
        /// Initialise the command.
        /// </summary>
        public CommandModelBase()
        {
            routedCommand_ = new RoutedCommand();
        }

        /// <summary>
        /// Default implementation always allows the command to execute.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanExecute(e.Parameter);
            e.Handled = true;
        }

        /// <summary>
        /// Subclasses must provide the execution logic.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void OnExecute(object sender, ExecutedRoutedEventArgs e)
        {
            Execute(e.Parameter);
        }

        #region ICommand Members

        public virtual bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public abstract void Execute(object parameter);

        #endregion
    }

تعليقات و اقتراحات لتحسين سيكون موضع ترحيب للغاية.

وكنت قد بحثت في هذه المسألة قبل بضعة أشهر، وكتبت على تمديد العلامات التي لا حيلة. ويمكن استخدامه مثل العادية ملزمة:

<Window.InputBindings>
    <KeyBinding Key="E" Modifiers="Control" Command="{input:CommandBinding EditCommand}"/>
</Window.InputBindings>

ويمكن الاطلاع على كامل شفرة المصدر لهذا التمديد هنا:

HTTP: // www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/

ويرجى الانتباه إلى أن هذا الحل قد لا يكون للغاية "نظيفة"، لأنه يستخدم بعض الفئات الخاصة والمجالات من خلال انعكاس ...

الجواب القصير هو لا يمكنك التعامل مباشرة مدخلات لوحة المفاتيح الأحداث دون البرمجية-خلف, ولكن يمكنك التعامل معها InputBindings مع MVVM (أنا يمكن أن تظهر لك مجموعة ذات الصلة سبيل المثال إذا كان هذا هو ما تحتاج إليه).

يمكنك توفير المزيد من المعلومات بشأن ما تريد القيام به في معالج ؟

التعليمات البرمجية-خلف لا يمكن تجنبها تماما مع MVVM.انها ببساطة لاستخدامها بدقة UI-المهام ذات الصلة.الكاردينال سبيل المثال سوف يكون لها نوع من 'نموذج إدخال البيانات ، عند تحميلها ، يحتاج إلى تعيين التركيز إلى أول عنصر المدخلات (مربع نص أو مربع تحرير وسرد ، أيا كان).كنت عادة تعيين هذا العنصر x:اسم السمة ، ثم ربط نافذة/الصفحة/UserControl هو 'تحميل' الحدث إلى تعيين التركيز إلى عنصر.هذا هو موافق تماما من نمط لأن المهمة هو واجهة المستخدم تتمحور حول و لا علاقة له مع البيانات تمثله.

وأعرف أن هذا السؤال هو قديم جدا، ولكن جئت بهذا لأن هذا النوع من الوظائف كان أدلى به للتو أسهل في التنفيذ في (5) سيلفرلايت. ولذلك ربما يكون البعض الآخر سوف يأتي بها هنا أيضا.

ولقد كتبت هذا الحل بسيط بعد لم أجد ما كنت أبحث عنه. اتضح أنها كانت بسيطة إلى حد ما. يجب أن تعمل في كل من سيلفرلايت 5 و WPF.

public class KeyToCommandExtension : IMarkupExtension<Delegate>
{
    public string Command { get; set; }
    public Key Key { get; set; }

    private void KeyEvent(object sender, KeyEventArgs e)
    {
        if (Key != Key.None && e.Key != Key) return;

        var target = (FrameworkElement)sender;

        if (target.DataContext == null) return;

        var property = target.DataContext.GetType().GetProperty(Command, BindingFlags.Public | BindingFlags.Instance, null, typeof(ICommand), new Type[0], null);

        if (property == null) return;

        var command = (ICommand)property.GetValue(target.DataContext, null);

        if (command != null && command.CanExecute(Key))
            command.Execute(Key);
    }

    public Delegate ProvideValue(IServiceProvider serviceProvider)
    {
        if (string.IsNullOrEmpty(Command))
            throw new InvalidOperationException("Command not set");

        var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

        if (!(targetProvider.TargetObject is FrameworkElement))
            throw new InvalidOperationException("Target object must be FrameworkElement");

        if (!(targetProvider.TargetProperty is EventInfo))
            throw new InvalidOperationException("Target property must be event");

        return Delegate.CreateDelegate(typeof(KeyEventHandler), this, "KeyEvent");
    }

والاستعمال:

<TextBox KeyUp="{MarkupExtensions:KeyToCommand Command=LoginCommand, Key=Enter}"/>

لاحظ أن Command هو سلسلة وليس ICommand التي يمكن ربطها. أعرف أن هذا ليس كما مرونة، لكنه أنظف عند استخدامها، وما تحتاج 99٪ من الوقت. على الرغم من أنه لا ينبغي أن يكون مشكلة للتغيير.

وعلى غرار الجواب karlipoppins، ولكنني وجدت أنه لم ينجح من دون الإضافات / التغييرات التالية:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top