问题
Ok, that probably is a pretty dumb question but I have searched quite a while but could not find a solution for this that works...
I have a Custom control inherited from Control
, which shall include code behind automation.
For examble select all text of a controls TextBox
when selected, or generate a list of close matches when the content of that TextBox
is changed.
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();
}
}
}
And the controls Style Template:
<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>
Question 1: How can I bind the event SelectContent
(to select all TextBox
content when it get focus, nb: it is part of a DataGrid
for the CellEditingTemplate
) to GotKeyboardFocus
? Events are normaly fine in the Apps code, but for the Custom Control they do not work as there is no "Code Behind" really for the Style...
Question 2: Assuming I have a dependency Property containing an array of words. Based on the content of the TextBox
, I would like to select a few words from the Array in the Dependency Property and pass them to a ListBox
in the Custom Control (the Content of the ListBox
shall only be managed by the Custom Control, not by anyone using that control. Is there a prefered/canonical MVVM schema on how to implement this?
回答1:
Usually you should post only one question, not multiple. Regarding first one you can use EventSetter
e.g. in implicit Style
in UserControl
's resources:
<Style TargetType="TextBox">
<EventSetter Event="GotKeyboardFocus" Handler="SelectContent"/>
</Style>
Regarding second question - implement a property, which is subset of your list and do update it accordingly e.g. if dependency property was changed(see property changed callback) or some another values were changed which the subset depends on.
Alternatively you could use a behavior for the TextBox
and handle events you need there. See e.g. select all behavior:
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();
}
}
Using this behavior in 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>
回答2:
Partial Solution:
Finaly I got event on the direct controls to work (controls in a ContextMenu
still don't get EventHandlers...).
Apparently the point was using GetTemplateChild()
in order to get the TextBox by name, and then associate the Event handlers:
<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>
And Code (Dependency Properties not shown):
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();
}
}
}
}
来源:https://stackoverflow.com/questions/60841649/handle-events-in-custom-control-code-behind