Handle mouseclick/doubleclick/mousedown and raise to child - wpf

I'm using a Thumb inside a ListBoxItem to move the item around a Canvas.
The thumb cover the entire surface of the ListBoxItem.
Since the ListBoxItem can contain for example a TextBox, i'd like to intercept double clicks (at the moment swallowed by the Thumb) and relay them directly to the Texbox.
I tried with a behavior with no luck (the event is not received by the targetElement)... here's the code, any help?
public sealed class BubbleDoubleClickEvent : Behavior<UIElement>
{
#region TargetElement
public UIElement TargetElement
{
get { return (UIElement)GetValue( TargetElementProperty ); }
set { SetValue( TargetElementProperty, value ); }
}
// Using a DependencyProperty as the backing store for TargetElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetElementProperty =
DependencyProperty.Register( "TargetElement", typeof( UIElement ), typeof( BubbleDoubleClickEvent ) );
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseDown += AssociatedObject_PreviewMouseDown;
}
private void AssociatedObject_PreviewMouseDown( object sender, MouseButtonEventArgs e )
{
if( e.ClickCount == 2 )
{
e.Handled = true;
var e2 = new MouseButtonEventArgs( e.MouseDevice, e.Timestamp, e.ChangedButton );
e2.RoutedEvent = Control.PreviewMouseDoubleClickEvent;
var target = TargetElement ?? AssociatedObject;
target.RaiseEvent( e2 );
}
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseDown -= AssociatedObject_PreviewMouseDown;
base.OnDetaching();
}
}
The Listbox defines the ItemContainerStyle template similar to this:
<Grid>
<ContentPresenter x:Name="contentPresenter" Content="{Binding}" ContentTemplate="{Binding ..., Path=ItemTemplate }" />
<Thumb x:Name="moveThumb" >
<i:Interaction.Behaviors>
<behaviors:BubbleDoubleClickEvent TargetElement="{Binding ElementName=contentPresenter}"/>
</i:Interaction.Behaviors>
</Thumb>
</Grid>

Related

How to enable an editing adorner when the adorned element is disabled in TreeView?

WPF
I have a TreeView (see below) for which I have created an Editing Adorner.
All works correctly. However, I would like to disable the TreeView while editing is in progress. When the TreeView is disabled -- as well as the element being adorned for editing -- the adorner is also being disabled.
How can the TreeView be disabled (IsEnabled = false) but still allow the Adorner to be enabled for editing?
TIA
namespace Doctor_Desk.Views.Adorners
{
public class EditSelectedItemAdorner : Adorner
{
private VisualCollection _Visuals;
private UIElement _UIElement;
private bool _IsCancel = false;
private TextBox _Textbox;
protected override int VisualChildrenCount
{
get
{
return _Visuals.Count;
}
}
// Be sure to call the base class constructor.
public EditSelectedItemAdorner(UIElement adornedElement, IConsultantTreeItem selectedItem)
: base(adornedElement)
{
_UIElement = adornedElement;
adornedElement.Visibility = Visibility.Hidden;
_Textbox = new TextBox
{
Background = Brushes.Pink,
Text = selectedItem.GetText()
};
// The VisualCollection will hold the content of this Adorner.
_Visuals = new VisualCollection(this)
{
// The _Textbox is a logical child of the VisualCollection of the Adorner. The ArrangeOverride and MeasureOverride
// will set up the Grid control for correct rendering.
_Textbox // Adding a single control for display.
};
}
/// The adorner placement is always relative to the top left corner of the adorned element. The children of the adorner are located by
/// implementing ArrangeOverride. The only other way to position the Adorner content is to use the AdornerPanel Class
/// with it's AdornerPlacementCollection Methods.
///
/// Overriding the default ArrangeOverride and MeasureOverride allows a control to be placed and diplayed in the VisualCollection.
protected override Size ArrangeOverride(Size finalSize)
{
//TextBox child = _Visuals[0] as TextBox;
//FrameworkElement element = _UIElement as FrameworkElement;
//if (element != null)
//{
// Size textBoxSize = new Size(Math.Max(150, element.DesiredSize.Width), Math.Max(30, element.DesiredSize.Height));
// Point location = new Point((this.Width - textBoxSize.Width) / 2,
// (textBoxSize.Height - this.Height) / 2);
// child.Arrange(new Rect(location, textBoxSize));
//}
TextBox child = _Visuals[0] as TextBox;
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
child.Arrange(adornedElementRect);
return base.ArrangeOverride(finalSize);
}
// Overriding the default ArrangeOverride and MeasureOverride allows a control to be diplayed in the VisualCollection.
protected override Size MeasureOverride(Size constraint)
{
_Textbox.Measure(constraint);
return _Textbox.DesiredSize;
}
protected override Visual GetVisualChild(int index)
{
return _Visuals[index];
}
// A common way to implement an adorner's rendering behavior is to override the OnRender
// method, which is called by the layout system as part of a rendering pass.
protected override void OnRender(DrawingContext drawingContext)
{
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
// Some arbitrary drawing implements.
SolidColorBrush renderBrush = new SolidColorBrush(Colors.Green)
{
Opacity = 0.2
};
Pen renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
double renderRadius = 5.0;
// Draw a circle at each corner.
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.TopRight, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomLeft, renderRadius, renderRadius);
drawingContext.DrawEllipse(renderBrush, renderPen, adornedElementRect.BottomRight, renderRadius, renderRadius);
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(EditSelectedItemAdorner), new PropertyMetadata(string.Empty));
}
}
<TreeView Grid.Row="2" Grid.Column="2" Grid.RowSpan="4" Width="400"
ItemsSource="{Binding SpecialityTree}"
>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tree:SpecialityTreeItem}" ItemsSource="{Binding Offices}" >
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type tree:OfficeTreeItem}" ItemsSource="{Binding Doctors}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type tree:DoctorTreeItem}">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<i:Interaction.Behaviors>
<b:ConsultantTreeViewBehavior
EditSelectedItem="{Binding IsEditing}"
SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}"
Text="{Binding EditingResult, Mode=OneWayToSource}"
/>
</i:Interaction.Behaviors>
namespace Doctor_Desk.Views.Behaviors
{
public class ConsultantTreeViewBehavior : Behavior<TreeView>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
#region SelectedItem Property
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ConsultantTreeViewBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is TreeViewItem item)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
#region [EditSelectedItem]
public bool EditSelectedItem
{
get { return (bool)GetValue(EditSelectedItemProperty); }
set { SetValue(EditSelectedItemProperty, value); }
}
private EditSelectedItemAdorner _Adorner;
public AdornerLayer myAdornerLayer { get; set; }
// Using a DependencyProperty as the backing store for EditSelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EditSelectedItemProperty =
DependencyProperty.Register("EditSelectedItem", typeof(bool), typeof(ConsultantTreeViewBehavior), new PropertyMetadata(false, OnEditSelectedItem));
private static void OnEditSelectedItem(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var d = sender as ConsultantTreeViewBehavior;
var h = d.AssociatedObject as TreeView;
TreeViewItem tvi = h
.ItemContainerGenerator
.ContainerFromItemRecursive(h.SelectedItem);
if (tvi is UIElement myItem)
{
d._Adorner = new EditSelectedItemAdorner(myItem, (IConsultantTreeItem)h.SelectedItem);
// Must have BindingMode.TwoWay for the Adorner to update this ConsultantTreeViewBehavior.
var bindingText = new Binding
{
Source = d,
Path = new PropertyPath(TextProperty),
Mode = BindingMode.TwoWay
};
// The TextProperty binds the Text from the EditSelectedItemAdorner to the ConsultantManagerViewModel.
BindingOperations.SetBinding(d._Adorner, EditSelectedItemAdorner.TextProperty, bindingText);
d.myAdornerLayer = AdornerLayer.GetAdornerLayer(myItem);
d.myAdornerLayer.Add(d._Adorner);
}
}
}
#endregion
#region[Text - xaml binding]
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ConsultantTreeViewBehavior), new PropertyMetadata(string.Empty,
(s, e) =>
{
}));
#endregion
}
}
Well, in leu of any other answer, it would appear that disabling selection in the TreeView disables all the elements of the TreeView -- including the selected branch. When the selected branch is disabled, so is its editing adorner. Therefore, my workaround is simply to cover that part of the TreeView which is being displayed.
In particular, in the OnRender override, add:
// Find offset of selected item from top of the tree.
GeneralTransform gt = AdornedElement.TransformToVisual(_TreeView);
Point offset_to_tree_top = gt.Inverse.Transform(new Point(0, 0));
drawingContext.DrawRectangle(Brushes.DimGray, null, new Rect(
offset_to_tree_top, _TreeView.DesiredSize));
This puts a DimGray color over the whole tree except for the editing adorner. Thus, user selection of any other tree item can not occur.

WPF handle double click event on Thumb and raise it as new event on another control

I tried with a behavior with no luck
Behavior:
public sealed class BubbleDoubleClickEvent : Behavior<UIElement>
{
#region TargetElement
public UIElement TargetElement
{
get { return (UIElement)GetValue( TargetElementProperty ); }
set { SetValue( TargetElementProperty, value ); }
}
// Using a DependencyProperty as the backing store for TargetElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TargetElementProperty =
DependencyProperty.Register( "TargetElement", typeof( UIElement ), typeof( BubbleDoubleClickEvent ) );
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseDown += AssociatedObject_PreviewMouseDown;
}
private void AssociatedObject_PreviewMouseDown( object sender, MouseButtonEventArgs e )
{
if( e.ClickCount == 2 )
{
e.Handled = true;
var e2 = new MouseButtonEventArgs( e.MouseDevice, e.Timestamp, e.ChangedButton );
e2.RoutedEvent = Control.PreviewMouseDoubleClickEvent;
var target = TargetElement ?? AssociatedObject;
target.RaiseEvent( e2 );
}
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseDown -= AssociatedObject_PreviewMouseDown;
base.OnDetaching();
}
}
XAML:
<Grid>
<ContentPresenter x:Name="contentPresenter" Content="{Binding}" ContentTemplate="{Binding ..., Path=ItemTemplate }" />
<Thumb x:Name="moveThumb" >
<i:Interaction.Behaviors>
<behaviors:BubbleDoubleClickEvent TargetElement="{Binding ElementName=contentPresenter}"/>
</i:Interaction.Behaviors>
</Thumb>
</Grid>
Any help appreciated
The code above works just well, but the target control did not receive the event OnPreviewMouseDoubleClick so i had to add an instance handler to manage the event like this:
this.AddHandler( Control.PreviewMouseDoubleClickEvent,
new MouseButtonEventHandler( ( target, args ) =>
{
//do stuff
args.Handled = true;
} ), false );

Mouseover button binding in WPF

What technique could be used in WPF to show TextBlock Text based on the Button that has mouse over on it?
The UI is organized as follows:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="Item1" Tag="This is the text for Item1"/>
<Button Content="Item2" Tag="This is the text for Item2"/>
<Button Content="Item3" Tag="This is the text for Item3"/>
</StackPanel>
<TextBlock Grid.Column="2" Margin="20,0,0,0" TextWrapping="Wrap" Text="This text should be shown based on the mouseovered button"/>
</Grid>
I was thinking to set the desired text to Tag of the Button but how to get that Tag to show on the mouse over event.
Notes:
I prefer to use individual Buttons here instead of ListBox or any other ItemsControl (the real application has more controls between these things etc.)
The application follows MVVM
I would prefer an approach other that setting the texts to UI directly (Tag of the Button)
For Mouse Move over,
Create an AttachedProperty for MouseMove and hook your list view with the property. The attached property can be used to any element in your application.
Attached Property
public class MouseBehaviour
{
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseMoveCommandChanged)));
private static void MouseMoveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseMove += new MouseEventHandler(element_MouseMove);
}
static void element_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseMoveCommand(element);
command.Execute(e);
}
public static void SetMouseMoveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseMoveCommandProperty, value);
}
public static ICommand GetMouseMoveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseMoveCommandProperty);
}
}
XAML
xmlns:mousebehav="clr-namespace:your namespace"
<Button mousebehav:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
VM
private RelayCommand _MouseMoveCommand;
public RelayCommand MouseMoveCommand
{
get
{
if (_MouseMoveCommand== null) return _MouseMoveCommand= new RelayCommand(param => Execute_MouseMoveCommand((MouseEventArgs)param));
return _MouseMoveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseMoveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
Attached property for Mouse Leave,
public static readonly DependencyProperty MouseLeaveCommandProperty =
DependencyProperty.RegisterAttached("MouseLeaveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseLeaveCommandChanged)));
private static void MouseLeaveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseLeave += new MouseEventHandler(element_MouseLeave);
}
static void element_MouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseLeaveCommand(element);
command.Execute(e);
}
public static void SetMouseLeaveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseLeaveCommandProperty, value);
}
public static ICommand GetMouseLeaveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseLeaveCommandProperty);
}
VM
private RelayCommand _MouseLeaveCommand;
public RelayCommand MouseLeaveCommand
{
get
{
if (_MouseLeaveCommand== null) return _MouseLeaveCommand= new RelayCommand(param => Execute_MouseLeaveCommand((MouseEventArgs)param));
return _MouseLeaveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseLeaveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
XAML
<Button mousebehav:MouseBehaviour.MouseLeaveCommand="{Binding MouseLeaveCommand}">

WP7 Custom Control Binding Inner Control

I'm creating a custom control that has a PasswordBox in it. How do I hook up a DependencyProperty of my custom control to the Password property of the PasswordBox?
From all the examples I see, hooking it up the password in the template using TemplateBinding should do the trick, but this doesn't seem to be working. What am I missing?
generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControlBinding="clr-namespace:CustomControlBinding">
<Style TargetType="CustomControlBinding:PasswordBoxTest">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CustomControlBinding:PasswordBoxTest">
<Grid Background="Transparent">
<PasswordBox Password="{TemplateBinding Text}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
PasswordBoxTest.cs
namespace CustomControlBinding
{
public class PasswordBoxTest : Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof( string ), typeof( PasswordBoxTest ), new PropertyMetadata( OnTextPropertyChanged ) );
public string Text
{
get { return GetValue( TextProperty ) as string; }
set { SetValue( TextProperty, value ); }
}
public PasswordBoxTest()
{
DefaultStyleKey = typeof( PasswordBoxTest );
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
private static void OnTextPropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
{
}
}
}
You have to use RelativeSource bindings, for it to work. Or you have to set the DataContext of your UserControl to this.
TemplateBinding is ok. You need to set somehow the binding source (for example through mentioned DataContext or simply in Xaml using Text attribute), but I cannot judge where's the problem since you omitted this code.
I don't know what's the purpose of this class, but probably adding some features to the standard PasswordBox. I would keep both implementations as similar as possible. What I mean the Text property should be called Password etc.
On more remark: The presented template does not need the Grid. Unless you have extra reason for using it, remove it - it just adds on the layout complexity. Note that the default template of the PasswordBox is already wrapped in an identical Grid...
I haven't been able to get this to work no matter what I do. What I did instead that does work is setting up some fake binding in code.
namespace CustomControlBinding
{
public class PasswordBoxTest : Control
{
private PasswordBox passwordBox;
private string passwordSetBeforeTemplateApplied;
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register( "Password", typeof( string ), new PropertyMetadata( OnPasswordPropertyChanged ) );
public string Password
{
get { return GetValue( PasswordProperty ) as string; }
set { SetValue( PasswordProperty, value ); }
}
public PasswordBoxTest()
{
DefaultStyleKey = typeof( PasswordBoxTest );
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
passwordBox = (PasswordBox)GetTemplateChild( "PasswordElement" );
passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
if( !string.IsNullOrEmpty( passwordSetBeforeTemplateApplied ) )
{
passwordBox.Password = passwordSetBeforeTemplateApplied;
}
}
public static void OnPasswordPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
( (WatermarkPasswordBox)d ).OnPasswordChanged( d, e );
}
private void OnPasswordChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
if( passwordBox == null )
{
passwordSetBeforeTemplateApplied = Password;
return;
}
if( Password != passwordBox.Password )
{
passwordBox.Password = Password;
}
}
private void PasswordBoxPasswordChanged( object sender, RoutedEventArgs e )
{
if( passwordBox.Password != Password )
{
Password = passwordBox.Password;
}
}
}
}

MVVM- How can I bind to a property, which is not a DependancyProperty?

I have found this question MVVM and the TextBox's SelectedText property. However, I am having trouble getting the solution given to work. This is my non-working code, in which I am trying to display the first textbox's selected text in the second textbox.
View:
SelectedText and Text are just string properties from my ViewModel.
<TextBox Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" Height="155" HorizontalAlignment="Left" Margin="68,31,0,0" Name="textBox1" VerticalAlignment="Top" Width="264" AcceptsReturn="True" AcceptsTab="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
<TextBox Text="{Binding SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" Height="154" HorizontalAlignment="Left" Margin="82,287,0,0" Name="textBox2" VerticalAlignment="Top" Width="239" />
TextBoxHelper
public static class TextBoxHelper
{
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if (e.OldValue == null && e.NewValue != null)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else if (e.OldValue != null && e.NewValue == null)
{
tb.SelectionChanged -= tb_SelectionChanged;
}
string newValue = e.NewValue as string;
if (newValue != null && newValue != tb.SelectedText)
{
tb.SelectedText = newValue as string;
}
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
}
What am I doing wrong?
The reason this is not working is that the property change callback isn't being raised (as the bound value from your VM is the same as the default value specified in the metadata for the property). More fundamentally though, your behavior will detach when the selected text is set to null. In cases like this, I tend to have another attached property that is simply used to enable the monitoring of the selected text, and then the SelectedText property can be bound. So, something like so:
#region IsSelectionMonitored
public static readonly DependencyProperty IsSelectionMonitoredProperty = DependencyProperty.RegisterAttached(
"IsSelectionMonitored",
typeof(bool),
typeof(PinnedInstrumentsViewModel),
new FrameworkPropertyMetadata(OnIsSelectionMonitoredChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsSelectionMonitored(TextBox d)
{
return (bool)d.GetValue(IsSelectionMonitoredProperty);
}
public static void SetIsSelectionMonitored(TextBox d, bool value)
{
d.SetValue(IsSelectionMonitoredProperty, value);
}
private static void OnIsSelectionMonitoredChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
if ((bool)e.NewValue)
{
tb.SelectionChanged += tb_SelectionChanged;
}
else
{
tb.SelectionChanged -= tb_SelectionChanged;
}
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
#region "Selected Text"
public static string GetSelectedText(DependencyObject obj)
{
return (string)obj.GetValue(SelectedTextProperty);
}
public static void SetSelectedText(DependencyObject obj, string value)
{
obj.SetValue(SelectedTextProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedTextProperty =
DependencyProperty.RegisterAttached(
"SelectedText",
typeof(string),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged));
private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
tb.SelectedText = e.NewValue as string;
}
}
static void tb_SelectionChanged(object sender, RoutedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
SetSelectedText(tb, tb.SelectedText);
}
}
#endregion
And then in your XAML, you'd have to add that property to your first TextBox:
<TextBox ... local:TextBoxHelper.IsSelectionMonitored="True" local:TextBoxHelper.SelectedText="{Binding SelectedText, Mode=OneWayToSource}" />
In order for the SelectedTextChanged handler to fire the SelectedText property must have an initial value. If you don't initialize this to some value (string.Empty as a bare minimum) then this handler will never fire and in turn you'll never register the tb_SelectionChanged handler.
This works for me using the class TextBoxHelper. As other mentioned, you need to initialize the SelectedText property of TextBoxHelper with a non null value. Instead of data binding to a string property (SelText) on the view you should bind to a string property of your VM which should implement INotifyPropertyChanged.
XAML:
<Window x:Class="TextSelectDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextSelectDemo"
Height="300" Width="300">
<StackPanel>
<TextBox local:TextBoxHelper.SelectedText="{Binding Path=SelText, Mode=TwoWay}" />
<TextBox Text="{Binding Path=SelText}" />
</StackPanel>
</Window>
Code behind:
using System.ComponentModel;
using System.Windows;
namespace TextSelectDemo
{
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
SelText = string.Empty;
DataContext = this;
}
private string _selText;
public string SelText
{
get { return _selText; }
set
{
_selText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelText"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Your binding attempts to bind the Text property of your TextBox to a SelectedText property of the TextBox's current data context. Since you're working with an attached property, not with a property hanging off of your data context, you will need to give more information in your binding:
<TextBox Text="{Binding local:TextBoxHelper.SelectedText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" ... />
Where local has been associated with the CLR namespace containing the TextBoxHelper class.
You need a normal .net property wrapper for the dependencyproperty, some like:
public string SelectedText
{
set {SetSelectedText(this, value);}
...
It is not required by runtime (runtime use set/get) but it is required by designer and compiler.

Resources