Set all binded TextBoxes to trim [duplicate] - c#

This question already has answers here:
Clear whitespace from end of string in WPF/XAML
(2 answers)
Closed 1 year ago.
I'm creating a WPF application using MVVM. I'd like to make it so all textboxes in the application, by default trims the text.
I have tried to follow the answer here
I managed to add System.Windows.Interactivty reference via NuGet. I created a UserControl in a behaviors folder and copied the provided code. But Visual Studio cannot find suitable method to override, and AssociatedObject does not exist.
And in the XAML, it does not like <local:TrimTextBoxBehavior /> or xmlns:local="clr-namespace:StaffApp;assembly=mscorlib" or xmlns:local="clr-namespace:StaffApp.Behaviors;assembly=mscorlib"
I have tried a different method of trimming all the binded properties' setters in my Model
e.g. public string MiddleNames { get => _middleNames; set => _middleNames = value.Trim(); }
But I'm not a fan of having to do this for every property, and this causes issues when the textbox is null as defined from my XAML form:
<Label Width="100" Content="Middle name(s)" />
<TextBox Text="{Binding Employee.MiddleNames, TargetNullValue=''}" />

You need a ValueConverter or an attached behavior that you apply via a Style to all TextBox controls. Third option would be to extend the TextBox and override TextBoxBase.OnTextChanged(TextChangedEventArgs).
TextTrimBehavior:
public class TextTrimBehavior : DependencyObject
{
#region IsEnabled attached property
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled", typeof(bool), typeof(TextTrimBehavior), new PropertyMetadata(false, TextTrimBehavior.OnAttached));
public static void SetIsEnabled(DependencyObject attachingElement, bool value)
{
attachingElement.SetValue(TextTrimBehavior.IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject attachingElement)
{
return (bool) attachingElement.GetValue(TextTrimBehavior.IsEnabledProperty);
}
#endregion
private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is TextBox attachedTextBox))
{
return;
}
if ((bool) e.NewValue)
{
attachedTextBox.LostFocus += TextTrimBehavior.TrimText;
}
else
{
attachedTextBox.LostFocus -= TextTrimBehavior.TrimText;
}
}
private static void TrimText(object sender, RoutedEventArgs e)
{
if (sender is TextBox textBox)
{
textBox.Text = textBox.Text.Trim();
}
}
}
TextBox Style:
<Style TargetType="TextBox">
<Setter Property="TextTrimBehavior.IsEnabled"
Value="True" />
</Style>
Since the Style has no key it will apply implicitly to all TextBox controls within the scope. To make the style global you have to put it into the App.xaml ResourceDictionary.
Extending the implicit style using Style.BasedOn:
<Style x:Key="ExplicitStyle" TargetType="TextBox"
BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Background"
Value="YellowGreen" />
</Style>
Alternatively you can set the attached property locally
<TextBox TextTrimBehavior.IsEnabled="True"
Text="{Binding Employee.MiddleNames, TargetNullValue=''}" />

You could try using Converters. This way you just have to add the converter to the binding of the textbox and that would do it.
// Property in the View Model
public string Text { get;set; }
// Converter class
public class TrimTextConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (!string.IsNullOrEmpty((string)value)) {
return ((string)value).Trim();
}
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return value;
}
}
<!--In the xaml file-->
<!--Reference to the converter namespace-->
xmlns:converter="clr-namespace:namespace-where-converter-is-located"
<!--Adding Converter To Resource Dictionary-->
<ResourceDictionary>
<converter:TrimTextConverter x:Key="TrimTextConverter"/>
</ResourceDictionary>
<!--TextBox-->
<TextBox Grid.Row="4" Text="{Binding Text, Converter={StaticResource TrimTextConverter}">

This is my code to trim text after lost keyboard focus
public class TextBoxTrimBehavior : Behavior<TextBox>
{
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
}
void AssociatedObject_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var tb = sender as TextBox;
if (tb != null && tb.Text != tb.Text?.Trim())
{
tb.Text = tb.Text?.Trim();
}
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
}
}
and use the behavior in xaml as follow
<TextBox>
<i:Interaction.Behaviors>
<behavior:TextBoxTrimBehavior />
</i:Interaction.Behaviors>
</TextBox>

Related

How to set value to property from attached dependency property?

I'm trying to show selected items of treeview in textblock. This is my XAML code
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="vm:HLViewModel.SelectedNode" Value="{Binding ElementName="tree",Path=SelectedItem}"/>
</Trigger>
</Style.Triggers>
</Style>
Here is my textblock where I'm trying to show selected Item
<TextBlock Text="{Binding myText}"/>
I have created attached dependencyproperty which will be set when Treeview's IsSelected property is triggered. How Can I set the value of myText in the callback function?
public class HLViewModel : DependencyObject
{
public myText{get;set;}
public static object GetSelectedNode(DependencyObject obj)
{
return (object)obj.GetValue(SelectedNodeProperty);
}
public static void SetSelectedNode(DependencyObject obj, object value)
{
obj.SetValue(SelectedNodeProperty, value);
}
public static readonly DependencyProperty SelectedNodeProperty =
DependencyProperty.RegisterAttached("SelectedNode", typeof(object), typeof(HLViewModel), new PropertyMetadata("def",SelectedNode_changed));
private static void SelectedNode_changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// wanna set of myText property value here
}
Here's the simple way to have a viewmodel use the selected item from a TreeView:
XAML:
<TreeView
x:Name="MyTreeView"
SelectedItemChanged="MyTreeView_SelectedItemChanged"
Codebehind:
private void MyTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
(DataContext as MyViewModel).SelectedRoomLevelItem = e.NewValue;
}
Here's a much more sophisticated way of doing the same thing. This example demonstrates how attached properties are used in WPF. Note that setting TreeViewAttached.SelectedItem will not set the selected item of the tree view -- if you want to do that, it's doable, but troublesome. All this attached property does is allow you to write bindings which receive the selected item from the treeview when the selection changes.
public static class TreeViewAttached
{
#region TreeViewAttached.SelectedItem Attached Property
public static Object GetSelectedItem(TreeView obj)
{
return (Object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(TreeView obj, Object value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(Object), typeof(TreeViewAttached),
new FrameworkPropertyMetadata(null) {
BindsTwoWayByDefault = true
});
#endregion TreeViewAttached.SelectedItem Attached Property
#region TreeViewAttached.MonitorSelectedItem Attached Property
public static bool GetMonitorSelectedItem(TreeView obj)
{
return (bool)obj.GetValue(MonitorSelectedItemProperty);
}
public static void SetMonitorSelectedItem(TreeView obj, bool value)
{
obj.SetValue(MonitorSelectedItemProperty, value);
}
public static readonly DependencyProperty MonitorSelectedItemProperty =
DependencyProperty.RegisterAttached("MonitorSelectedItem", typeof(bool), typeof(TreeViewAttached),
new PropertyMetadata(false, MonitorSelectedItem_PropertyChanged));
private static void MonitorSelectedItem_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
(d as TreeView).SelectedItemChanged += TreeViewAttached_SelectedItemChanged;
}
else
{
(d as TreeView).SelectedItemChanged -= TreeViewAttached_SelectedItemChanged;
}
}
private static void TreeViewAttached_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SetSelectedItem(sender as TreeView, e.NewValue);
}
#endregion TreeViewAttached.MonitorSelectedItem Attached Property
}
XAML:
<TreeView
local:TreeViewAttached.MonitorSelectedItem="True"
local:TreeViewAttached.SelectedItem="{Binding SelectedRoomLevelItem}"
ItemsSource="{Binding Items}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding HeaderText}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Label Content="{Binding Path=SelectedRoomLevelItem.HeaderText}" />
My example uses a quickie treeview item datacontext class with Items and HeaderText properties. Your use of this code will have to adapt to the particular viewmodel classes in your project.

WPF UserControl with nullable dependency property dependent on checkbox value

I'm trying to create a custom UserControl that displays the properties of a complex object as a form. Additionally, if the user unchecks a checkbox in the header of the UserControl, the value of the dependency property should be null (but the form values should stay displayed, even if the form is disabled) - and vice versa.
I'm working with two dependency properties of type ComplexObject on the UserControl - one is public (this will be bound to by the client), another is private (its properties will be bound to the internal controls in the UserControl):
public ComplexObject ComplexObject
{
get { return (ComplexObject )GetValue(ComplexObjectProperty); }
set { SetValue(ComplexObjectProperty, value); }
}
private ComplexObject VisibleComplexObject
{
get { return (ComplexObject)GetValue(VisibleComplexObjectProperty); }
set { SetValue(VisibleComplexObjectProperty, value); }
}
Now I'm struggling with a binding between those two, so that CompexObject becomes either VisibleComplexObject or null based on the checkbox value. This should also work the other way. I've tried to solve this using DataTriggers in the Style of the UserControl, but was unable to do so:
<UserControl.Style>
<Style TargetType="local:CheckableComplexTypeGroup">
// 'CheckableComplexTypeGroup' TargetType does not match type of the element 'UserControl'
</Style>
</UserControl.Style>
Using <local:CheckableComplexTypeGroup.Style> instead of <UserControl.Style> didn't work either.
Are there any other suggestions? Or maybe another way of doing this?
Finally I've solved this without using plain old event handlers instead of binding/triggers.
CheckableComplexObjectGroup.xaml:
<UserControl Name="thisUC" ...>
<GroupBox>
<GroupBox.Header>
<CheckBox Name="cbComplexObject"
IsChecked="{Binding ElementName=thisUC, Path=ComplexObject, Mode=OneWay, Converter={StaticResource nullToFalseConv}}"
Checked="cbComplexObject_Checked" Unchecked="cbComplexObject_Unchecked"/>
</GroupBox.Header>
...
</UserControl>
CheckableComplexObjectGroup.cs:
public static readonly DependencyProperty ComplexObjectProperty =
DependencyProperty.Register("ComplexObject",
typeof(ComplexObject),
typeof(CheckableComplexObjectGroup),
new PropertyMetadata(null));
private static readonly DependencyProperty VisibleComplexObjectProperty =
DependencyProperty.Register("VisibleComplexObject",
typeof(ComplexObject),
typeof(CheckableComplexObjectGroup),
new PropertyMetadata(new ComplexObject()));
//...
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == ComplexObjectProperty)
{
if (null != e.NewValue)
VisibleComplexObject = ComplexObject;
}
base.OnPropertyChanged(e);
}
private void cbComplexObject_Checked(object sender, RoutedEventArgs e)
{
ComplexObject = VisibleComplexObject;
}
private void cbComplexObject_Unchecked(object sender, RoutedEventArgs e)
{
ComplexObject = null;
}

Update TextBlock with binding not working

I have a question about databinding!
I am writing code for a 'node editor' that has some (different) nodes in it.
I use a BaseViewModel class that derives from INotifyPropertyChanged.
There is a 'base' NodeViewModel (that derives from it) with an ObservableCollection and other Properties, like the Node's Name property. It's implementation looks like this:
(in public class NodeViewModel : BaseViewModel):
protected String mName = String.Empty;
public String Name {
get { return mName; }
set {
if (mName == value) {
return;
}
mName = value;
OnPropertyChanged("Name");
}
}
With an OnPropertyChanged handler that looks like this:
(in BaseViewModel)
protected virtual void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Now I have one additional RoomViewModel that derives from NodeViewModel.
I use another different ViewModel that I call RoomCollectionViewModel to group some rooms.
Now when I add a room to my roomcollection (by drawing a connection between them) I test all connected rooms for the same name.
If an already connected room exists in the collection with the same room name (e.g. "new room") I want to change those two room's names to e.g. "new room #1" and "new room #2". No problem so far.
Every node control (created using DataTemplates with set DataContext set to the ViewModel) contains a TextBlock (a modified one) that displays the node's name.
This is where it gets problematic:
I use a modified Textblock because I want to be able to modify the node's name by double-clicking on it. And that works perfectly, only if I modify the RoomViewModel's name in Code, this (modified) TextBlock won't update.
The strange thing is this:
When two equally named rooms in a collection get renamed by my code and I then double-click on the editable TextBlock (which converts to a TextBox in that process), I already see the modified Text. So I assume my DataBinding and my code is correct, just not complete :)
So how is it possible to force an update of my EditableTextBlock, the Text (DependencyProperty) seems to be updated correctly...
I hope you understand what my problem is! Thank you for any help.
Update 1
This is the XAML code for my EditableTextBlock (it comes from here: http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing)
<UserControl x:Class="NetworkUI.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NetworkUI"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="240" x:Name="mainControl">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox KeyDown="TextBox_KeyDown" Loaded="TextBox_Loaded" LostFocus="TextBox_LostFocus"
Text="{Binding ElementName=mainControl, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Margin="0" BorderThickness="1" />
</DataTemplate>
<DataTemplate x:Key="DisplayModeTemplate">
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsInEditMode" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
<Trigger Property="IsInEditMode" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And here is the code-behind file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace NetworkUI {
/// <summary>
/// Interaction logic for EditableTextBlock.xaml
/// </summary>
public partial class EditableTextBlock : UserControl {
#region Dependency Properties, Events
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(EditableTextBlock),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty IsEditableProperty =
DependencyProperty.Register("IsEditable", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(true));
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register("IsInEditMode", typeof(Boolean), typeof(EditableTextBlock), new PropertyMetadata(false));
public static readonly DependencyProperty TextFormatProperty =
DependencyProperty.Register("TextFormat", typeof(String), typeof(EditableTextBlock), new PropertyMetadata("{0}"));
#endregion ///Dependency Properties, Events
#region Variables and Properties
/// <summary>
/// We keep the old text when we go into editmode
/// in case the user aborts with the escape key
/// </summary>
private String oldText;
/// <summary>
/// Text content of this EditableTextBlock
/// </summary>
public String Text {
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock editable or not
/// </summary>
public Boolean IsEditable {
get { return (Boolean)GetValue(IsEditableProperty); }
set { SetValue(IsEditableProperty, value); }
}
/// <summary>
/// Is this EditableTextBlock currently in edit mode
/// </summary>
public Boolean IsInEditMode {
get {
if (IsEditable)
return (Boolean)GetValue(IsInEditModeProperty);
else
return false;
}
set {
if (IsEditable) {
if (value)
oldText = Text;
SetValue(IsInEditModeProperty, value);
}
}
}
/// <summary>
/// The text format for the TextBlock
/// </summary>
public String TextFormat {
get { return (String)GetValue(TextFormatProperty); }
set {
if (value == "")
value = "{0}";
SetValue(TextFormatProperty, value);
}
}
/// <summary>
/// The formatted text of this EditablTextBlock
/// </summary>
public String FormattedText {
get { return String.Format(TextFormat, Text); }
}
#endregion ///Variables and Properties
#region Constructor
/// <summary>
/// Default constructor for the editable text block
/// </summary>
public EditableTextBlock() {
InitializeComponent();
Focusable = true;
FocusVisualStyle = null;
}
#endregion ///Constructor
#region Methods, Functions and Eventhandler
/// <summary>
/// Invoked when we enter edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_Loaded(object sender, RoutedEventArgs e) {
TextBox txt = sender as TextBox;
/// Give the TextBox input focus
txt.Focus();
txt.SelectAll();
}
/// <summary>
/// Invoked when we exit edit mode
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_LostFocus(object sender, RoutedEventArgs e) {
IsInEditMode = false;
}
/// <summary>
/// Invoked when the user edits the annotation.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event arguments</param>
void TextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
IsInEditMode = false;
e.Handled = true;
}
else if (e.Key == Key.Escape) {
IsInEditMode = false;
Text = oldText;
e.Handled = true;
}
}
/// <summary>
/// Invoked when the user double-clicks on the textblock
/// to edit the text
/// </summary>
/// <param name="sender">Sender (the Textblock)</param>
/// <param name="e">Event arguments</param>
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
if (e.ClickCount == 2)
IsInEditMode = true;
}
#endregion ///Methods, Functions and Eventhandler
}
Thank you for any help!
Update 2
I changed the following line of code:
<TextBlock Text="{Binding ElementName=mainControl, Path=FormattedText}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
to:
<TextBlock Text="{Binding ElementName=mainControl, Path=Text}" Margin="5,3,5,3" MouseDown="TextBlock_MouseDown" />
and now it is working!
I didn't see the TextBlock using the FormattedText in the first place! Ugh, thank you very much, now everything updates perfectly!
As postes by Lee O. the problem was indeed the bound property from my EditableTextBlock control.
The TextBlock used the FormattedText property that was updated by my Binding. Now I use the Text property for both, the TextBlock and the TextBox controls.
I simply removed the FormattedText property as well as the TextFormatProperty (DependencyProperty) and TextFormat property from my EditableTextBlock because I didn't plan to use those.
Thank you again!

Attached properties/events

I have a question regarding custom attached properties/events. In my scenario I want to attach a property/event to any control. The value of this property/event should be an event handler. In short, it should look like:
<TextBox local:Dragging.OnDrag="OnDrag" />
First I tried to implement OnDrag as an attached property. This works for the case above, but then the following case fails:
<Style TargetType="TextBox">
<Setter Property="local:Dragging.OnDrag" Value="OnDrag" />
</Style>
Because the "OnDrag" string can apparently not be made into a RoutedEventHandler (the attached property's type) by the XAML system.
The next thing I tried then was to try and use an attached event, very much like the builtin Mouse.MouseEnter for example.
The complete code for this is shown at the bottom. There are curious things happening with this version:
If you run the code as shown (with the RegisterRoutedEvent line commented) it will show the "Add handler" function is called. Then the xaml system has an internal exception when applying the style (due to missing registered event I guess).
If you run the code with the RegisterRoutedEvent line in effect everything runs, but the "Add handler" function is never called. I want it to be called though, so that I can register at the drag and drop manager.
Curiously, if I change the event in the EventSetter from my own to Mouse.MouseEnter the code that's automatically generated by the xaml designer (in MainWindow.g[.i].cs) is different.
I am not sure why 2) does not call the AddXYZHandler. MSDN seems to indicate this should work.
Finally my questions:
How can I make this work? Is it possible at all?
Do I better use an attached event or an attached property for my scenario?
in case of properties: How do I fix the Style Setter so it converts the OnDrag string to a proper RoutedEventHandler?
in case of events: What's going wrong here? Any way to fix this? I want AddXYZHandler to be called, but apparently that does not work with the style.
MainWindow.xaml:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GridTest.MainWindow"
xmlns:local="clr-namespace:GridTest"
Title="MainWindow" Height="350" Width="525"
local:XYZTest.XYZ="OnXYZAttached">
<Window.Style>
<Style TargetType="Window">
<EventSetter Event="local:XYZTest.XYZ" Handler="OnXYZStyle" />
</Style>
</Window.Style>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace GridTest
{
public class XYZTest
{
//public static readonly RoutedEvent XYZEvent = EventManager.RegisterRoutedEvent("XYZ", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(XYZTest));
public static void AddXYZHandler(DependencyObject element, RoutedEventHandler handler)
{
MessageBox.Show("add handler");
}
public static void RemoveXYZHandler(DependencyObject element, RoutedEventHandler handler)
{
MessageBox.Show("remove handler");
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void OnXYZAttached(object sender, RoutedEventArgs e)
{
MessageBox.Show("attached");
}
public void OnXYZStyle(object sender, RoutedEventArgs e)
{
MessageBox.Show("style");
}
}
}
}
New code:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GridTest.MainWindow"
x:Name="root"
xmlns:local="clr-namespace:GridTest"
local:XYZTest.ABC="OnXYZTopLevel"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="Red" />
<Setter Property="local:XYZTest.ABC" Value="OnXYZStyle" />
<!-- <Setter Property="local:XYZTest.ABC" Value="{Binding OnXYZStyleProperty, ElementName=root}" /> -->
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
using System.Windows;
namespace GridTest
{
public class XYZTest
{
public static readonly DependencyProperty ABCProperty = DependencyProperty.RegisterAttached("ABC", typeof(RoutedEventHandler), typeof(XYZTest), new UIPropertyMetadata(null, OnABCChanged));
public static void SetABC(UIElement element, RoutedEventHandler value)
{
System.Diagnostics.Debug.WriteLine("ABC set to " + value.Method.Name);
}
static void OnABCChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("ABC changed to " + ((RoutedEventHandler)e.NewValue).Method.Name);
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[] { "A", "B", "C" };
}
public void OnXYZTopLevel(object sender, RoutedEventArgs e)
{
MessageBox.Show("handler top level");
}
public void OnXYZStyle(object sender, RoutedEventArgs e)
{
MessageBox.Show("handler style");
}
public RoutedEventHandler OnXYZStyleProperty
{
get { return OnXYZStyle; }
}
}
}
I successfully implemented drag and drop functionality completely using Attached Properties. If I were you, I'd avoid using custom events for this, as you're stuck with their parameters. Personally, I went for ICommand instead, but you could also use delegates.
Please look below at the list of properties and Commands that I used in my drag and drop base class implementation:
/// <summary>
/// Gets or sets the type of the drag and drop object required by the Control that the property is set on.
/// </summary>
public Type DragDropType { get; set; }
/// <summary>
/// Gets or sets the allowable types of objects that can be used in drag and drop operations.
/// </summary>
public List<Type> DragDropTypes { get; set; }
/// <summary>
/// Gets or sets the ICommand instance that will be executed when the user attempts to drop a dragged item onto a valid drop target Control.
/// </summary>
public ICommand DropCommand { get; set; }
/// <summary>
/// Gets or sets the DragDropEffects object that specifies the type of the drag and drop operations allowable on the Control that the property is set on.
/// </summary>
public DragDropEffects DragDropEffects { get; set; }
/// <summary>
/// The Point struct that represents the position on screen that the user initiated the drag and drop procedure.
/// </summary>
protected Point DragStartPosition
{
get { return dragStartPosition; }
set { if (dragStartPosition != value) { dragStartPosition = value; } }
}
/// <summary>
/// The UIElement object that represents the UI element that has the attached Adorner control... usually the top level view.
/// </summary>
protected UIElement AdornedUIElement
{
get { return adornedUIElement; }
set { if (adornedUIElement != value) { adornedUIElement = value; } }
}
The AdornedUIElement property holds an Adorner that displays the dragged items as they are dragged, but is optional for you to implement. In this base class, I have implemented most of the drag and drop functionality and exposed protected abstract methods that derived classes must implement. As an example, this method calls the OnAdornedUIElementPreviewDragOver method to provide derived classes an opportunity to change the behaviour of the base class:
private void AdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
PositionAdorner(e.GetPosition(adornedUIElement));
OnAdornedUIElementPreviewDragOver(sender, e); // Call derived classes here <<<
if (e.Handled) return; // to bypass base class behaviour
HitTestResult hitTestResult = VisualTreeHelper.HitTest(adornedUIElement, e.GetPosition(adornedUIElement));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
UpdateDragDropEffects(controlUnderMouse, e);
e.Handled = true;
}
/// <summary>
/// Must be overidden in derived classes to call both the UpdateDropProperties and UpdateDragDropEffects methods to provide feedback for the current drag and drop operation.
/// </summary>
/// <param name="sender">The Control that the user dragged the mouse pointer over.</param>
/// <param name="e">The DragEventArgs object that contains arguments relevant to all drag and drop events.</param>
protected abstract void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e);
Then in my extended ListBoxDragDropManager class:
protected override void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
HitTestResult hitTestResult = VisualTreeHelper.HitTest(AdornedUIElement, e.GetPosition(AdornedUIElement));
ListBox listBoxUnderMouse = hitTestResult.VisualHit.GetParentOfType<ListBox>();
if (listBoxUnderMouse != null && listBoxUnderMouse.AllowDrop)
{
UpdateDropProperties(ListBoxProperties.GetDragDropType(listBoxUnderMouse), ListBoxProperties.GetDropCommand(listBoxUnderMouse));
}
UpdateDragDropEffects(listBoxUnderMouse, e);
e.Handled = true; // This bypasses base class behaviour
}
Finally, it is used simply in the UI like so (the RelativeSource declarations and narrow width here make it seem worse than it is):
<ListBox ItemsSource="{Binding Disc.Tracks, IsAsync=True}" SelectedItem="{Binding
Disc.Tracks.CurrentItem}" AllowDrop="True" Attached:ListBoxProperties.
IsDragTarget="True" Attached:ListBoxProperties.DropCommand="{Binding
DataContext.DropTracks, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragDropTypes="{Binding
DataContext.DragDropTypes, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragEffects="{Binding
DataContext.DragEffects, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}">
I must be honest though... this was a lot of work. However, now that I can implement drag and drop operations with visual feedback just by setting a few properties, it totally seems worth it.

Call a command on a view model whenever any TextBox in the view changes

I have a few views that each have several XAML TextBox instances. The Text property of each is bound to a value object that represents the visual data model for the view.
<TextBox Text="{Binding Path=SelectedItem.SomeValue, UpdateSourceTrigger=PropertyChanged}"/>
I have about 9 or 10 of these boxes in a form. I have a class (ChangeModel) that keeps track of which forms have been altered (e.g. the user has entered in a new value). The problem is the actual value object that is bound to the TextBox.Text property (in the example that would be SelectedItem.SomeValue) can't access the ChangeModel.
I'd like to easily add a binding in the XML (maybe in the resources section) that will call a command in the view model whenever any TextBox changes. I think I can do this with a DataTrigger statement but I'm not sure how to go about it.
Can anyone describe how to use a data trigger or any other XAML mechanism to alert the view model whenever any TextBox within that view is altered?
Alternatively to that Markus Hütter said you can save a few lines of XAML and write custom behavior like this
public class InvokeCommandOnTextChanged : Behavior<TextBox>
{
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(InvokeCommandOnTextChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(InvokeCommandOnTextChanged));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var command = this.Command;
var param = this.CommandParameter;
if (command != null && command.CanExecute(param))
{
command.Execute(param);
}
}
}
Then you can use this behavior with your textboxes:
<TextBox>
<i:Interaction.Behaviors>
<b:InvokeCommandOnTextChanged Command="{Binding AddCommand}" />
</i:Interaction.Behaviors>
</TextBox>

Categories

Resources