I am trying to dynamically hide the Toggle Button on an Expander using a Property on my ViewModel set to the Visibility property. At least that is my thought. Is there a way to just alter that one setting on the ToggleButton control without having to do the ENTIRE template of the Toggle Button in my xaml?
You could use the Loaded event for the Expander and set up the Binding in the event handler or if you want a reusable way without code behind you could use an attached behavior.
The attached behavior finds the ToggleButton inside the Template and sets up the Binding to the attached property ToggleButtonVisibility.
Uploaded a sample app here: ExpanderToggleButtonVisibilityTest.zip
Use it like this
<Expander Name="expander"
behaviors:ExpanderBehavior.BindToggleButtonVisibility="True"
behaviors:ExpanderBehavior.ToggleButtonVisibility="{Binding YourVisibilityProperty}"
.../>
ExpanderBehavior
public class ExpanderBehavior
{
public static DependencyProperty BindToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("BindToggleButtonVisibility",
typeof(bool),
typeof(ExpanderBehavior),
new PropertyMetadata(false, OnBindToggleButtonVisibilityChanged));
public static bool GetBindToggleButtonVisibility(Expander expander)
{
return (bool)expander.GetValue(BindToggleButtonVisibilityProperty);
}
public static void SetBindToggleButtonVisibility(Expander expander, bool value)
{
expander.SetValue(BindToggleButtonVisibilityProperty, value);
}
private static void OnBindToggleButtonVisibilityChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Expander expander = target as Expander;
if (expander.IsLoaded == true)
{
BindToggleButtonVisibility(expander);
}
else
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
BindToggleButtonVisibility(expander);
expander.Loaded -= loadedEventHandler;
});
expander.Loaded += loadedEventHandler;
}
}
private static void BindToggleButtonVisibility(Expander expander)
{
ToggleButton headerSite = expander.Template.FindName("HeaderSite", expander) as ToggleButton;
if (headerSite != null)
{
Binding visibilityBinding = new Binding
{
Source = expander,
Path = new PropertyPath(ToggleButtonVisibilityProperty)
};
headerSite.SetBinding(ToggleButton.VisibilityProperty, visibilityBinding);
}
}
#region ToggleButtonVisibilityProperty
public static DependencyProperty ToggleButtonVisibilityProperty =
DependencyProperty.RegisterAttached("ToggleButtonVisibility",
typeof(Visibility),
typeof(ExpanderBehavior),
new PropertyMetadata(Visibility.Visible));
public static Visibility GetToggleButtonVisibility(Expander expander)
{
return (Visibility)expander.GetValue(ToggleButtonVisibilityProperty);
}
public static void SetToggleButtonVisibility(Expander expander, Visibility value)
{
expander.SetValue(ToggleButtonVisibilityProperty, value);
}
#endregion // ToggleButtonVisibilityProperty
}
Related
I am using a set of standard buttons, reused across a WPF app and want to add a shortcut key to each of the buttons.
So I have a ControlTemplate containing the buttons with Command bound to standard commands
I am trying to add a KeyBinding to the UserControl that contains the Button by adding Dependency Property to the Button and navigating up the Parent tree to the nearest UserControl.
I can get the DP values to set Key and Modifiers from the template Button, but cannot get hold of the Command (maybe because it is not bound until later??)
Any ideas how I can:
Either get hold of or create the Command from the Template per this approach
Or get the Command when it is resolved and then set the KeyBinding
PS: I have set the Key and Modifiers in separate DPs but would prefer to have a single DP of KeyBinding, then set ShortcutBinding.Key and ShortcutBinding.Modifers in XAML.
Is there a way to set the properties of a DP class in XAML like that?
Extract of the XAML of from the button group template:
<ctrl:ButtonShortcut
x:Name="btnUpdate"
Style="{StaticResource EditButtonStyle}"
Command="{Binding DataContext.UpdateCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Content="Update"
ShortcutKey="U"
ShortcutModifiers="Ctrl"/>
The DP class, inherited from Button, implementing the Key and Modifiers to link with the same Command that is bound to the Button:
public partial class ButtonShortcut : Button
{
public KeyBinding ShortcutBinding { get; set; }
public Key ShortcutKey
{
get { return (Key)GetValue(ShortcutKeyProperty); }
set { SetValue(ShortcutKeyProperty, value); }
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register("ShortcutKey", typeof(Key), typeof(ButtonShortcut), new PropertyMetadata(Key.None, ShortcutKeyChanged));
public ModifierKeys ShortcutModifiers
{
get { return (ModifierKeys)GetValue(ShortcutModifiersProperty); }
set { SetValue(ShortcutModifiersProperty, value); }
}
public static readonly DependencyProperty ShortcutModifiersProperty =
DependencyProperty.Register("ShortcutModifiers", typeof(ModifierKeys), typeof(ButtonShortcut), new PropertyMetadata(ModifierKeys.None, ShortcutKeyChanged));
private static void ShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var btn = d as ButtonShortcut;
if (btn != null)
{
FrameworkElement uc = btn.Parent as FrameworkElement;
while (uc?.Parent != null && uc is not UserControl)
uc = uc.Parent as FrameworkElement;
if (btn.ShortcutBinding == null)
{
btn.ShortcutBinding = new KeyBinding();
}
var bindings = btn.CommandBindings;
if (e.NewValue is Key)
btn.ShortcutBinding.Key = (Key)e.NewValue;
if (e.NewValue is ModifierKeys)
btn.ShortcutBinding.Modifiers = (ModifierKeys)e.NewValue;
//So far, so good, but I cannot find the Command to apply to the KeyBinding
btn.ShortcutBinding.Command = btn.Command;
btn.ShortcutBinding.CommandParameter = btn.CommandParameter;
if (btn.Command == null)
System.Diagnostics.Debug.Print("not in Commmand");
if (btn.CommandBindings.Count == 0)
System.Diagnostics.Debug.Print("not in CommandBindings");
if (btn.ReadLocalValue(CommandProperty) == DependencyProperty.UnsetValue)
System.Diagnostics.Debug.Print("not in DP CommandProperty");
if (btn.ShortcutBinding.Key != Key.None && uc != null)
{
if (!uc.InputBindings.Contains(btn.ShortcutBinding))
uc.InputBindings.Add(btn.ShortcutBinding);
}
}
}
public ButtonShortcut()
{
InitializeComponent();
}
}
This looks like a very bad smelling design. The child control should not configure the parent control. Especially not to add "features" that the child pretends to offer: in your case the button is not able to execute key gestures - it only defines them.
Since the parent control is the target of the gestures, it must define the required handlers to execute the gesture. This means, all the action and responsibilities are in the parent UserControl and not in the Button (or child element). Classes should never know details of other classes. A button should never know which parent will execute its command and how to register the command in the first place.
The button is just a passive source. It does not handle commands or gestures itself. Therefore, it would never register a CommandBinding or InputBinding.
Usually, you bind the Button.Command to a command defined on the command target. The button invokes this command on the target (or raises a RoutedCommand) and the target executes a corresponding operation.
It doesn't make sense to define commands or key gestures on a button. The button won't execute them. Commands, key and mouse gestures must be defined on the target, where the related responsibilities are.
You can let the parent UserControl (which I assume in this example is the command target) define a RoutedCommand. Register the corresponding key gesture(s) with this command:
Solution 1
MyUserControl.xaml.cs
partial class MyUserControl : UserControl
{
public static RoutedCommand DoActionCommand { get; }
static MyUserControl()
{
var gestures = new InputGestureCollection
{
new KeyGesture(Key.U, ModifierKeys.Control),
};
DoActionCommand = new RoutedUICommand(
"Do something",
nameof(DoActionCommand),
typeof(CommandTargetUserControl),
gestures);
}
public MyUserControl()
{
InitializeComponent();
this.CommandBindings.Add(new CommandBinding(DoActionCommand, ExecuteDoActionCommand));
}
}
CommandTargetUserControl.xaml
<MyUserControl>
<Button Command="{x:Static local:CommandTargetUserControl.DoActionCommand}" />
</MyUserControl>
Solution 2
To allow configuring default key gestures without modifying the source and the target, you can implement an attached behavior. This behavior basically delegates a hooked command, which is invoked by a key gesture, to the actual command sources that are registered with the particular key gesture. Command source and command target are totally decoupled. Input bindings are configured explicitly to respect encapsulation (no silent and unexpected modidifications).
Usage example
<Window local:KeyGestureDelegate.IsKeyGestureDelegationEnabled="True">
<local:KeyGestureDelegate.TargetKeyGestures>
<InputGestureCollection>
<KeyGesture>Ctrl+U</KeyGesture>
<KeyGesture>Ctrl+Shift+M</KeyGesture>
</InputGestureCollection>
</local:KeyGestureDelegate.TargetKeyGestures>
<StackPanel>
<Button Command="{Binding SomeCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Ctrl+U"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
<Button Command="{Binding SomeOtherCommand}"
local:KeyGestureDelegate.SourceKeyGesture="Shift+Ctrl+M"
local:KeyGestureDelegate.IsKeyGestureCommandExecutionEnabled="True" />
</StackPanel>
</Window>
Implementation example
KeyGestureDelegate.cs
An implementation of the RelayCommand used in this example can be found at Microsoft Docs: Relaying Command Logic.
public class KeyGestureDelegate : DependencyObject
{
// Custom KeyGesture comparer for the Dictionary
private class KeyGestureComparer : EqualityComparer<KeyGesture>
{
public override bool Equals(KeyGesture? x, KeyGesture? y)
=> (x?.Key, x?.Modifiers).Equals((y?.Key, y?.Modifiers));
public override int GetHashCode([DisallowNull] KeyGesture obj)
=> HashCode.Combine(obj.Key, obj.Modifiers);
}
private static ICommand KeyGestureDelegateCommand { get; } = new RelayCommand(ExecuteKeyGestureDelegateCommand);
public static bool GetIsKeyGestureDelegationEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureDelegationEnabledProperty);
public static void SetIsKeyGestureDelegationEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureDelegationEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureDelegationEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureDelegationEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureDelegationEnabled));
public static bool GetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement) => (bool)attachedElement.GetValue(IsKeyGestureCommandExecutionEnabledProperty);
public static void SetIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, bool value) => attachedElement.SetValue(IsKeyGestureCommandExecutionEnabledProperty, value);
public static readonly DependencyProperty IsKeyGestureCommandExecutionEnabledProperty = DependencyProperty.RegisterAttached(
"IsKeyGestureCommandExecutionEnabled",
typeof(bool),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(bool), OnIsKeyGestureCommandExecutionEnabled));
public static InputGestureCollection GetTargetKeyGestures(DependencyObject obj) => (InputGestureCollection)obj.GetValue(TargetKeyGesturesProperty);
public static void SetTargetKeyGestures(DependencyObject obj, InputGestureCollection value) => obj.SetValue(TargetKeyGesturesProperty, value);
public static readonly DependencyProperty TargetKeyGesturesProperty = DependencyProperty.RegisterAttached(
"TargetKeyGestures",
typeof(InputGestureCollection),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(InputGestureCollection), OnTargetKeyGesturesChanged));
public static KeyGesture GetSourceKeyGesture(DependencyObject attachedElement) => (KeyGesture)attachedElement.GetValue(SourceKeyGestureProperty);
public static void SetSourceKeyGesture(DependencyObject attachedElement, KeyGesture value) => attachedElement.SetValue(SourceKeyGestureProperty, value);
public static readonly DependencyProperty SourceKeyGestureProperty = DependencyProperty.RegisterAttached(
"SourceKeyGesture",
typeof(KeyGesture),
typeof(KeyGestureDelegate),
new PropertyMetadata(default(KeyGesture), OnSourceKeyGestureChanged));
// Remember added InputBindings to enable later removal
private static Dictionary<UIElement, IList<InputBinding>> InputBindingTargetMap { get; } = new Dictionary<UIElement, IList<InputBinding>>();
// Lookup command sources that map to a particular gesture
private static Dictionary<KeyGesture, IList<ICommandSource>> InputBindingSourceMap { get; } = new Dictionary<KeyGesture, IList<ICommandSource>>(new KeyGestureComparer());
private static void OnIsKeyGestureDelegationEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
InputGestureCollection gestures = GetTargetKeyGestures(keyGestureHandler);
if ((bool)e.NewValue)
{
RegisterKeyBinding(keyGestureHandler, gestures);
}
else
{
UnregisterKeyBinding(keyGestureHandler);
}
}
private static void OnIsKeyGestureCommandExecutionEnabled(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
KeyGesture keyGesture = GetSourceKeyGesture(attachedElement);
if ((bool)e.NewValue)
{
RegisterCommandBinding(commandSource, keyGesture);
}
else
{
UnregisterCommandBinding(commandSource, keyGesture);
}
}
private static void OnTargetKeyGesturesChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not UIElement keyGestureHandler)
{
throw new ArgumentException($"Attached element must be of type {typeof(UIElement)}.");
}
if (e.OldValue is InputBindingCollection)
{
UnregisterKeyBinding(keyGestureHandler);
}
if (!GetIsKeyGestureDelegationEnabled(keyGestureHandler))
{
return;
}
RegisterKeyBinding(keyGestureHandler, e.NewValue as InputGestureCollection);
}
private static void OnSourceKeyGestureChanged(DependencyObject attachedElement, DependencyPropertyChangedEventArgs e)
{
if (attachedElement is not ICommandSource commandSource)
{
throw new ArgumentException($"Attached element must be of type {typeof(ICommandSource)}.");
}
UnregisterCommandBinding(commandSource, e.OldValue as KeyGesture);
if (!GetIsKeyGestureCommandExecutionEnabled(attachedElement))
{
return;
}
RegisterCommandBinding(commandSource, e.NewValue as KeyGesture);
}
private static void ExecuteKeyGestureDelegateCommand(object commandParameter)
{
if (InputBindingSourceMap.TryGetValue(commandParameter as KeyGesture, out IList<ICommandSource> commandSources))
{
foreach (ICommandSource commandSource in commandSources)
{
ExecuteCommandSource(commandSource);
}
}
}
private static void ExecuteCommandSource(ICommandSource commandSource)
{
if (commandSource.Command is RoutedCommand routedCommand)
{
IInputElement commandTarget = commandSource.CommandTarget ?? commandSource as IInputElement;
if (routedCommand.CanExecute(commandSource.CommandParameter, commandTarget))
{
routedCommand.Execute(commandSource.CommandParameter, commandTarget);
}
}
else if (commandSource.Command?.CanExecute(parameter: commandSource.CommandParameter) ?? false)
{
commandSource.Command.Execute(commandSource.CommandParameter);
}
}
private static void RegisterKeyBinding(UIElement keyGestureHandler, InputGestureCollection inputGestureCollection)
{
if (inputGestureCollection == null)
{
return;
}
IList<InputBinding>? inputBindings = new List<InputBinding>();
InputBindingTargetMap.Add(keyGestureHandler, inputBindings);
foreach (KeyGesture gesture in inputGestureCollection.OfType<KeyGesture>())
{
var inputBinding = new KeyBinding(KeyGestureDelegateCommand, gesture) { CommandParameter = gesture };
keyGestureHandler.InputBindings.Add(inputBinding);
inputBindings.Add(inputBinding);
}
}
private static void UnregisterKeyBinding(UIElement keyGestureHandler)
{
if (InputBindingTargetMap.TryGetValue(keyGestureHandler, out IList<InputBinding>? inputBindings))
{
foreach (InputBinding inputBinding in inputBindings)
{
keyGestureHandler.InputBindings.Remove(inputBinding);
}
InputBindingTargetMap.Remove(keyGestureHandler);
}
}
private static void RegisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (!InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources = new List<ICommandSource>();
InputBindingSourceMap.Add(keyGesture, commandSources);
}
commandSources.Add(commandSource);
}
private static void UnregisterCommandBinding(ICommandSource commandSource, KeyGesture keyGesture)
{
if (keyGesture == null)
{
return;
}
if (InputBindingSourceMap.TryGetValue(keyGesture, out IList<ICommandSource>? commandSources))
{
commandSources.Remove(commandSource);
if (!commandSources.Any())
{
InputBindingSourceMap.Remove(keyGesture);
}
}
}
}
I'm using MVVMCross in my crossplatform native Xamarin app. I seem to have a problem binding boolean properties in my custom control to boolean properties in my ViewModel. For example:
My custom control BarCodeTextBox.cs:
public sealed class BarCodeTextBox : TextBox
{
public BarCodeTextBox()
{
this.DefaultStyleKey = typeof(BarCodeTextBox);
}
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set {
SetValue(IsListeningForCodesProperty, value);
if (value)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}
}
// Using a DependencyProperty as the backing store for IsListening. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox), new PropertyMetadata(false));
The viewmodel of the page BoxCloseViewModel.cs:
public class BoxCloseViewModel : ViewModelBase
{
private bool m_isManualBoxCodeInput;
public bool IsManualBoxCodeInput
{
get { return m_isManualBoxCodeInput; }
set { SetProperty(ref m_isManualBoxCodeInput, value); }
}
}
The binding in BoxCloseView.xaml:
<Controls:BarCodeTextBox x:Name="BarCodeInput" Text="{Binding BoxCode, Mode=TwoWay}" IsListeningForCodes="{Binding IsManualBoxCodeInput, Mode=OneWay, Converter={StaticResource BoolToInverseBool}}"/>
When I change the value of IsManualBoxCodeInput in the ViewModel nothing happens in the control (IsListeningForCodes does not change). Things I checked:
The converter works perfectly (and removing it does not solve the issue). In fact the converter is called when the ViewModel property changes (I'm able to debug it).
When I change the value of IsListeningForCodes in the page's code behind, it works (the control shows the change).
When I do the exact same thing with a string property, everything works perfectly.
There are no binding errors in the Output log.
PropertyChangedEvent is fired correctly with IsManualBoxCodeInput property.
I've realized the same thing happened to another control with a boolean property which used to work, after migrating to MVVMCross no longer does.
Bindings don't call the setter property of a DependencyObject. If you want some code to execute when a binding changes you need to add it as a callback of the PropertyMetadata.
public bool IsListeningForCodes
{
get { return (bool)GetValue(IsListeningForCodesProperty); }
set { SetValue(IsListeningForCodesProperty, value); }
}
public static readonly DependencyProperty IsListeningForCodesProperty =
DependencyProperty.Register("IsListeningForCodes", typeof(bool), typeof(BarCodeTextBox),
new PropertyMetadata(false, OnIsListeningForCodesChanged));
static void OnIsListeningForCodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (BarCodeTextBox)d;
instance.OnIsListeningForCodesChanged();
}
void OnIsListeningForCodesChanged()
{
if (IsListeningForCodes)
{
IsReadOnly = true;
PrefixElement.Visibility = Visibility.Collapsed;
Window.Current.CoreWindow.CharacterReceived += CoreWindow_CharacterReceived;
}
else
{
IsReadOnly = false;
Focus(FocusState.Keyboard);
PrefixElement.Visibility = Visibility.Visible;
Window.Current.CoreWindow.CharacterReceived -= CoreWindow_CharacterReceived;
}
Text = string.Empty;
}
Hi I'm trying to bind to the TextBox.CaretIndex property which isn't a DependencyProperty, so I created a Behavior, but it doesn't work as expected.
Expectation (when focused)
default = 0
if I change the value in my view it should change the value in my viewmodel
if I change the value in my viewmodel it should change the value in my view
Current behavior
viewmodel value gets called ones when the window opens
Code-behind
public class TextBoxBehavior : DependencyObject
{
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register(
"CursorPosition",
typeof(int),
typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(
default(int),
new PropertyChangedCallback(CursorPositionChanged)));
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
// breakpoint get never called
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
// breakpoint get never called
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
private static void CursorPositionChanged(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
// breakpoint get never called
//var textBox = dependencyObject as TextBox;
//if (textBox == null) return;
}
}
XAML
<TextBox Text="{Binding TextTemplate,UpdateSourceTrigger=PropertyChanged}"
local:TextBoxBehavior.CursorPosition="{Binding CursorPosition}"/>
Further Information
I think there is something really wrong here because I need to derive it from DependencyObject which was never needed before, because CursorPositionProperty is already a DependencyProperty, so this should be enough. I also think I need to use some events in my Behavior to set my CursorPositionProperty correctly, but I don't know which.
After fighting with my Behavior i can present you a 99% working solution
Behavior
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfMVVMTextBoxCursorPosition
{
public class TextBoxCursorPositionBehavior : DependencyObject
{
public static void SetCursorPosition(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CursorPositionProperty, i);
}
public static int GetCursorPosition(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CursorPositionProperty);
}
public static readonly DependencyProperty CursorPositionProperty =
DependencyProperty.Register("CursorPosition"
, typeof(int)
, typeof(TextBoxCursorPositionBehavior)
, new FrameworkPropertyMetadata(default(int))
{
BindsTwoWayByDefault = true
,DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
}
);
public static readonly DependencyProperty TrackCaretIndexProperty =
DependencyProperty.RegisterAttached(
"TrackCaretIndex",
typeof(bool),
typeof(TextBoxCursorPositionBehavior),
new UIPropertyMetadata(false
, OnTrackCaretIndex));
public static void SetTrackCaretIndex(DependencyObject dependencyObject, bool i)
{
dependencyObject.SetValue(TrackCaretIndexProperty, i);
}
public static bool GetTrackCaretIndex(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(TrackCaretIndexProperty);
}
private static void OnTrackCaretIndex(DependencyObject dependency, DependencyPropertyChangedEventArgs e)
{
var textbox = dependency as TextBox;
if (textbox == null)
return;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (!oldValue && newValue) // If changed from false to true
{
textbox.SelectionChanged += OnSelectionChanged;
}
else if (oldValue && !newValue) // If changed from true to false
{
textbox.SelectionChanged -= OnSelectionChanged;
}
}
private static void OnSelectionChanged(object sender, RoutedEventArgs e)
{
var textbox = sender as TextBox;
if (textbox != null)
SetCursorPosition(textbox, textbox.CaretIndex); // dies line does nothing
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
vm:TextBoxCursorPositionBehavior.TrackCaretIndex="True"
vm:TextBoxCursorPositionBehavior.CursorPosition="{Binding CursorPosition,Mode=TwoWay}"/>
<TextBlock Height="50" Text="{Binding CursorPosition}"/>
there is just on thing i don't know why it doesn't work => BindsTwoWayByDefault = true. it has no effect on the binding as far as i can tell you because of this i need to set the binding mode explicit in XAML
I encountered a similar problem, and the easiest solution for me was to inherit from TextBox and add a DependencyProperty. So it looks like this:
namespace UI.Controls
{
public class MyTextBox : TextBox
{
public static readonly DependencyProperty CaretPositionProperty =
DependencyProperty.Register("CaretPosition", typeof(int), typeof(MyTextBox),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnCaretPositionChanged));
public int CaretPosition
{
get { return (int)GetValue(CaretPositionProperty); }
set { SetValue(CaretPositionProperty, value); }
}
public MyTextBox()
{
SelectionChanged += (s, e) => CaretPosition = CaretIndex;
}
private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyTextBox).CaretIndex = (int)e.NewValue;
}
}
}
... and in my XAML:
xmlns:controls="clr-namespace:IU.Controls"
...
<controls:MyTextBox CaretPosition="{Binding CaretPosition}"/>
... and CaretPosition property in the View Model of course. If you're not going to bind your View Model to other text-editing controls, this may be sufficient, if yes - you'll probably need another solution.
The solution from WiiMaxx has the following problems for me:
The caret index in the text box is not changed when the view model property is changed from the code.
This was also mentioned by Tejas Vaishnav in his comment to the solution.
BindsTwoWayByDefault = true does not work.
He stated that it is strange that he needs to inherit from DependencyObject.
The TrackCaretIndex property is only used for initialisation and it felt kind of unnecessary.
Here is my solution which solves those problems:
Behavior
public static class TextBoxAssist
{
// This strange default value is on purpose it makes the initialization problem very unlikely.
// If the default value matches the default value of the property in the ViewModel,
// the propertyChangedCallback of the FrameworkPropertyMetadata is initially not called
// and if the property in the ViewModel is not changed it will never be called.
private const int CaretIndexPropertyDefault = -485609317;
public static void SetCaretIndex(DependencyObject dependencyObject, int i)
{
dependencyObject.SetValue(CaretIndexProperty, i);
}
public static int GetCaretIndex(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(CaretIndexProperty);
}
public static readonly DependencyProperty CaretIndexProperty =
DependencyProperty.RegisterAttached(
"CaretIndex",
typeof(int),
typeof(TextBoxAssist),
new FrameworkPropertyMetadata(
CaretIndexPropertyDefault,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
CaretIndexChanged));
private static void CaretIndexChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
if (dependencyObject is not TextBox textBox || eventArgs.OldValue is not int oldValue || eventArgs.NewValue is not int newValue)
{
return;
}
if (oldValue == CaretIndexPropertyDefault && newValue != CaretIndexPropertyDefault)
{
textBox.SelectionChanged += SelectionChangedForCaretIndex;
}
else if (oldValue != CaretIndexPropertyDefault && newValue == CaretIndexPropertyDefault)
{
textBox.SelectionChanged -= SelectionChangedForCaretIndex;
}
if (newValue != textBox.CaretIndex)
{
textBox.CaretIndex = newValue;
}
}
private static void SelectionChangedForCaretIndex(object sender, RoutedEventArgs eventArgs)
{
if (sender is TextBox textBox)
{
SetCaretIndex(textBox, textBox.CaretIndex);
}
}
}
XAML
<TextBox Height="50" VerticalAlignment="Top"
Name="TestTextBox"
Text="{Binding MyText}"
viewModels:TextBoxAssist.CaretIndex="{Binding CaretIndex}"/>
Some clarifications for the differences:
View model property changes work now because the caret index on the TextBox is set at the end of CaretIndexChanged.
The BindsTwoWayByDefault was fixed by using the according FrameworkPropertyMetadata constructor parameter.
Inheriting from DependencyObject was only necessary because DependencyProperty.Register was used instead of DependencyProperty.RegisterAttached.
Without the TrackCaretIndex property I had the problem that the propertyChangedCallback for the FrameworkPropertyMetadata was never called to properly initialize things. The problem occurs only when the default value for the FrameworkPropertyMetadata match the value of the view model property right from the start and the view model property is not changed. That's why I used this random default value.
As you said, the TextBox.CaretIndex Property is not a DependencyProperty, so you cannot data bind to it. Even with your own DependencyProperty, it won't work... how would you expect to be notified when TextBox.CaretIndex Property changes?
Premise: I read all the others threads about similar issues but none of those solved my problem.
I have a UserControl (SummarySource) with 3 DP:
public static DependencyProperty QueryProperty;
public static DependencyProperty MaxRowsPerPageProperty;
public static DependencyProperty OpcSessionProperty;
And the respective public getters and setters:
[Category("Common")]
public String Query
{
get { return (String)GetValue(QueryProperty); }
set { SetValue(QueryProperty, value); }
}
[Category("Common")]
public UInt32 MaxRowsPerPage
{
get { return (UInt32)GetValue(MaxRowsPerPageProperty); }
set { SetValue(MaxRowsPerPageProperty, value); }
}
[Category("Common")]
public UaSession OpcSession
{
get { return (UaSession)GetValue(OpcSessionProperty); }
set { SetValue(OpcSessionProperty, value); }
}
The problem is that tha PropertyChanged Callback for the "OpcSession" variable (the only one that is a custom type) isn't fired.
Static Constructor
OpcSessionProperty = DependencyProperty.Register("OpcSession", typeof(UaSession), typeof(SummarySource), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnSessionChanged)));
The Callback
private static void OnSessionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("He3e1");
SummarySource thisControl = (SummarySource)sender;
if (thisControl.DataContext != null)
{
((DataRetriever)thisControl.DataContext).SetOpcSession((UaSession)e.NewValue);
}
}
The MessageBox is never showed. If I put a MessageBox.Show on the other callbacks I can see the message when Load the form that use the control or change the value in xaml.
The .Xaml
<Window.DataContext>
<cs:UaSession x:Name="opcSession" EndpointUrl="opc.tcp://192.168.200.11:62543/Runtime"/>
</Window.DataContext>
<control:SummarySource x:Key="qq" MaxRowsPerPage="25" OpcSession="{Binding Path=DataContext, ElementName=window, PresentationTraceSources.TraceLevel=High, Mode=TwoWay}" />
No Binding Errors in output
In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}