【问题标题】:Handle Events in Custom Control Code Behind在自定义控制代码中处理事件
【发布时间】:2020-03-25 01:08:04
【问题描述】:

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

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

例如,在选中时选择控件 TextBox 的所有文本,或者在更改 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(在获得焦点时选择所有TextBox 内容,注意:它是CellEditingTemplateDataGrid 的一部分) 到GotKeyboardFocus?事件在应用程序代码中通常很好,但对于自定义控件,它们不起作用,因为样式没有真正的“代码隐藏”......

问题 2: 假设我有一个包含单词数组的依赖属性。根据TextBox的内容,我想从依赖属性中的数组中选择几个词,并将它们传递给自定义控件中的ListBoxListBox的内容只能由自定义控件,而不是使用该控件的任何人。是否有关于如何实现此功能的首选/规范 MVVM 架构?

【问题讨论】:

    标签: wpf events automation custom-controls code-behind


    【解决方案1】:

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

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

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

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

    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>
    

    【讨论】:

    • 嗨 Rekshino,我将样式添加到 TextBox,但构建失败,声称它需要 x:Class 指令,并且无论如何处理程序将被标记为在设计器中找不到......请参阅代码问题。
    • 查看我的答案,把它放到 UserControl 的资源中。处理程序也必须在UserControl
    • 如果我真的不明白,请原谅我。但是该控件是一个自定义控件,其样式为Generic.xaml,而不是UserControl,后面有代码。因此我没有UserControl xaml 文件...我错过了什么吗?
    • 我添加了一个行为示例,它选择了GotFocus 上的TextBox 中的所有文本。
    • 我不会说行为与事件,而是行为与代码背后。我认为行为不是背后的代码,因为它是一个独立的功能,可以在 XAML 中使用声明性。我尝试从不使用背后的代码,而只使用行为。所以对我来说没有经验法则。
    【解决方案2】:

    部分解决方案:

    最后我得到了关于直接控制工作的事件(ContextMenu 中的控制仍然没有得到 EventHandlers...)。

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

    <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();
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-23
      • 2011-04-17
      • 2018-05-28
      • 1970-01-01
      相关资源
      最近更新 更多