在后面的自定义控件代码中处理事件

Handle Events in Custom Control Code Behind

好吧,这可能是一个非常愚蠢的问题,但我已经搜索了很长时间,但找不到有效的解决方案...

我有一个继承自 Control 的自定义控件,其中应包含自动化代码。

例如 select 控件 TextBox 的所有文本 selected 时,或者当 TextBox 的内容更改时生成接近匹配列表。

public class vokDataGridEdit : Control
{
    static vokDataGridEdit()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));

        // Events internal to control (??? found on some how-to's)
        EventManager.RegisterClassHandler(typeof(vokDataGridEdit), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(OnSelectContent), true);
    }

    // Dependecy Properties ...

    // The Event that shall Fire when the TextBox gets Focus / Editing Mode
    public static void SelectContent(object sender, RoutedEventArgs e)
    {
        if (sender is TextBox tb)
        {
            tb.SelectAll();
        }
    }
}

以及控件样式模板:

<ResourceDictionary xmlns       = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x     = "http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ccont = "clr-namespace:App.Controls">

    <!-- Default style for the Validation Buttons -->
    <Style TargetType="{x:Type ccont:vokDataGridEdit}">

        <Setter Property="SnapsToDevicePixels"  Value="true" />

        <Setter Property="Template">
            <Setter.Value>

                <ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">

                    <TextBox Text                               = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
                             BorderThickness                    = "0"
                             ContextMenuService.Placement       = "Right"
                             ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
                             GotKeyboardFocus                   = "SelectContent">

                        <TextBox.ContextMenu>
                            <ContextMenu>
                                <ContextMenu.Template>
                                    <ControlTemplate>

                                        <Border CornerRadius    = "5"
                                                Background      = "LightGray"
                                                BorderThickness = "1" 
                                                BorderBrush     = "Gray"
                                                Padding         = "2">

                                            <StackPanel Orientation="Vertical">

                                                <!-- Title -->
                                                <TextBlock Text="Test" />

                                                <!-- TODO: List of matches -->
                                                <TextBox Text               = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}" 
                                                         BorderThickness    = "0" />

                                            </StackPanel>

                                        </Border>

                                    </ControlTemplate>
                                </ContextMenu.Template>
                            </ContextMenu>
                        </TextBox.ContextMenu>

                    </TextBox>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

问题 1: 如何绑定事件 SelectContent (到 select 所有 TextBox 内容,当它获得焦点时,nb:它是 DataGrid 的一部分 CellEditingTemplate) 到 GotKeyboardFocus?事件在 Apps 代码中正常,但对于自定义控件,它们不起作用,因为没有 "Code Behind" 真正用于样式...

问题 2: 假设我有一个包含单词数组的依赖项 属性。基于TextBox的内容,我想select从Dependency属性中的Array中提取几个词,并将它们传递给Custom Control中的ListBoxListBox 的内容只能由自定义控件管理,而不是由任何使用该控件的人管理。是否有关于如何实现它的 prefered/canonical MVVM 架构?

通常你应该post只做一个问题,而不是多个问题。关于第一个,您可以使用 EventSetter 例如在 UserControl 的资源中隐式 Style

<Style TargetType="TextBox">
    <EventSetter Event="GotKeyboardFocus" Handler="SelectContent"/>
</Style>

关于第二个问题 - 实施 属性,它是您列表的子集,并相应地更新它,例如如果依赖项 属性 已更改(请参阅 属性 已更改的回调)或子集所依赖的其他一些值已更改。

或者,您可以使用 TextBox 的行为并在那里处理您需要的事件。参见例如select 所有行为:

public class SelectAllBehavior : Behavior<TextBox>
{
    private bool _doSelectAll = false;
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.GotFocus += AssociatedObject_GotFocus;
        AssociatedObject.PreviewMouseUp += AssociatedObject_MouseUp;
        AssociatedObject.PreviewMouseDown += AssociatedObject_MouseDown;
    }

    private void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (_doSelectAll)
        {
            AssociatedObject.Dispatcher.BeginInvoke((Action) (()=>{ AssociatedObject.SelectAll(); }));
        }
        _doSelectAll = false;
    }

    private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        _doSelectAll = !AssociatedObject.IsFocused;
    }

    private void AssociatedObject_GotFocus(object sender, System.Windows.RoutedEventArgs e)
    {
        AssociatedObject.SelectAll();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
        AssociatedObject.PreviewMouseUp -= AssociatedObject_MouseUp;
        AssociatedObject.PreviewMouseDown -= AssociatedObject_MouseDown;
        base.OnDetaching();
    }
}

在 XAML 中使用此行为:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Text="Some text">
    <i:Interaction.Behaviors>
        <local:SelectAllBehavior/>
    </i:Interaction.Behaviors>
</TextBox>

部分解:

我终于让直接控件上的事件起作用了(ContextMenu 中的控件仍然没有 EventHandlers...)。

显然,重点是使用 GetTemplateChild() 以按名称获取 TextBox,然后关联事件处理程序:

<ResourceDictionary xmlns       = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x     = "http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ccont = "clr-namespace:App.Controls">

    <!-- Default style for the Validation Buttons -->
    <Style TargetType="{x:Type ccont:vokDataGridEdit}">

        <Setter Property="SnapsToDevicePixels"  Value="true" />

        <Setter Property="Template">
            <Setter.Value>

                <ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">

                    <TextBox Text                               = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
                             BorderThickness                    = "0"
                             ContextMenuService.Placement       = "Right"
                             ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
                             x:Name                             = "TextBox">

                        <TextBox.ContextMenu>
                            <ContextMenu x:Name="Menu">
                                <ContextMenu.Template>
                                    <ControlTemplate>

                                        <Border CornerRadius    = "5"
                                                Background      = "LightGray"
                                                BorderThickness = "1" 
                                                BorderBrush     = "Gray"
                                                Padding         = "2">

                                            <StackPanel Orientation="Vertical">

                                                <!-- Title -->
                                                <TextBlock Text="Test" x:Name = "Test" />

                                                <!-- TODO: List of matches -->
                                                <TextBox Text               = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}" 
                                                         BorderThickness    = "0" />

                                            </StackPanel>

                                        </Border>

                                    </ControlTemplate>
                                </ContextMenu.Template>
                            </ContextMenu>
                        </TextBox.ContextMenu>

                    </TextBox>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

和代码(未显示依赖属性):

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace App.Controls
{
    /// <summary>
    /// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
    /// </summary>
    public class vokDataGridEdit : Control
    {
        static vokDataGridEdit()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Demo purpose only, check for previous instances and remove the handler first  
            if (this.GetTemplateChild("TextBox") is TextBox button)
            {
                button.PreviewMouseLeftButtonDown   += this.SelectContentPreparation;
                button.GotKeyboardFocus             += this.SelectContent;
                button.MouseDoubleClick             += this.SelectContent;
                //button.GotFocus                     += this.SelectContent;
            }
        }

        /// <summary>
        /// Prepare the Control to ensure it has focus before subsequent event fire
        /// </summary>
        private void SelectContentPreparation(object sender, MouseButtonEventArgs e)
        {
            if (sender is TextBox tb)
            {
                if (!tb.IsKeyboardFocusWithin)
                {
                    e.Handled = true;
                    tb.Focus();
                }
            }
        }

        private void SelectContent(object sender, RoutedEventArgs e)
        {
            if (sender is TextBox tb)
            {
                e.Handled = true;
                tb.SelectAll();
            }
        }
    }
}