How to update UserControl Dependency Property with binding in WPF MVVM? - c#

I have a custom user control named CharacteristicSlider with CharValue property that displays the current characteristic value.
public partial class CharacteristicSlider : UserControl
{
.....
public int CharValue
{
get => (int)GetValue(CharValueProperty);
set => SetValue(CharValueProperty, value);
}
public static readonly DependencyProperty CharValueProperty =
DependencyProperty.Register("CharValue", typeof(int),
typeof(CharacteristicSlider),
new PropertyMetadata(0, OnCharValuePropertyChanged));
private static void OnCharValuePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as CharacteristicSlider;
if (control is null) return;
control.CharValue = (int)e.NewValue;
}
......
}
I use this control in my MainWindow like this
<components:CharacteristicSlider components:CharValue="{Binding Strength}"
x:Name="StrengthSlider" components:CharName="Strength"
Margin="40,20,40,20" Command="{Binding UpdateCharValue}" />
The strength property is just a wrap around external class library Character class. OnPropertyChanged invokes the PropertyChanged event of INotityPropertyChanged. I checked with debugger and the property names passes correctly, it is "Strength"
public int Strength
{
get => _character.Strength;
set
{
_character.Strength = value;
OnPropertyChanged();
}
}
The problem is that components:CharValue="{Binding Strength}" part does not work and does not update the CharValue at all.
But if I bind MainWindow's title to Strength property (Title="{Binding Strength}") the title of the window is updated, while the slider's stays the same. Why the slider's DependencyProperty is never updated though I have a binding?

Related

Bind ViewModel's property to UserControl's child control and itself DependencyProperty

What I want to do is to bind my usercontrol's label to a property value in ViewModel.
But I also want to get notified when the label changed ( and to do other work like extract the label's new value to modify grid width and so on).
How to do this?
what I did is:
Have a viewmodel with an Voltage property, which is what I want to display.
UnitVm.cs
private int m_V;
public int VoltInVm
{
get
{ return m_V; }
set
{
if (m_V != value)
{
Set<int>(ref m_V, value, nameof(Volt));
}
}
}
and my usercontrol: Unit.cs
public partial class Unit : UserControl
{
public static readonly DependencyProperty VoltProperty =
DependencyProperty.Register("Volt", typeof(int), typeof(Unit),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender, (o, e) => ((Unit)o).OnVoltChanged(o, e)));
private void OnVoltChanged(double dVolt)
{
double dWidth;
if (double.TryParse(strVal, out dWidth))
{
dWidth = dVolt / 380 * 100;
if (dWidth > 100)
dWidth = 100;
gridVolt.ColumnDefinitions[0].Width = new GridLength(dWidth, GridUnitType.Star);
gridVolt.ColumnDefinitions[1].Width = new GridLength(100 - dWidth, GridUnitType.Star);
}
}
public int Volt
{
get { return (int)GetValue(VoltProperty); }
set
{
SetValue(VoltProperty, value);
}
}
the DependencyProperty of VoltProperty is defined, and the work I want to do is written inside OnVoltChanged.
I mean when accepting change from ViewModel, I can call OnVoltChanged.
To use the usercontrol of Unit in a main window:
<DockPanel DataContext="{Binding UnitVm, Source={StaticResource Locator}}">
<Viewbox
<Label x:Name="lblVolt" Content="{Binding VoltInVm}" />
</Viewbox>
</DockPanel>
lblVolt binding to UnitVm context can update with new voltage values correctly.
But how to bind to DependencyProperty of Volt in Unit?
And is this the right way?
Thanks in advance.
Ting
The view model property setter is not implemented correctly.
It should look like shown below, i.e. use the correct property name nameof(VoltInVm) and probably not check m_V != value before calling Set, because that is already done by the Set method.
private int voltInVm;
public int VoltInVm
{
get { return voltInVm; }
set { Set<int>(ref voltInVm, value, nameof(VoltInVm)); }
}
When you now bind the UserControl's property like
<local:Unit Volt="{Binding VoltInVm}"/>
the PropertyChangedCallback of the Volt dependency property will be called each time the VoltInVm property changes its value.
It is however unclear what (o, e) => ((Unit)o).OnVoltChanged(o, e) in the dependency property registration is supposed to be. It should certainly look like this:
(o, e) => ((Unit)o).OnVoltChanged((int)e.NewValue)
and the method should be declared with an int argument instead of double:
private void OnVoltChanged(int volt) ...
Or - certainly better - change all voltage property types to double, in the control and in the view model.

How to binding User Control in MVVM Caliburn.Micro?

I have a User Control(next UC) with label. I need on button click change a UC label content. On UC codebehind i create DependencyProperty and methods to change a label.
public string InfoLabel
{
get
{
return (string)this.GetValue(InfoLabelProperty);
}
set
{
this.SetValue(InfoLabelProperty, value);
}
}
private static void InfoLabelChangeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
uc.CInfoLabel.Content = uc.InfoLabel;
}
public static readonly DependencyProperty InfoLabelProperty = DependencyProperty.Register("InfoLabel", typeof(string), typeof(UserControl1), new PropertyMetadata("", new PropertyChangedCallback(InfoLabelChangeCallback)));
On ShellView i got Binding on control and button.
<c:UserControl1 InfoLabel="{Binding InfoLabel1}" />
<Button x:Name="ChangeUserControllButton"/>
On ShellViewModel I have Binding InfoLabel1.
private string infoLabel= "something";
public string InfoLabel1
{
get
{
return infoLabel;
}
set
{
infoLabel = value;
}
}
public void ChangeUserControllButton()
{
InfoLabel1 = "Hello world";
}
The problem is When a UC is initialize, then it`s work. I mean label from UC will have content "something", but when I Click on button, content not changing to "Hello world". How to make it right?
View model needs to implement INotifyPropertyChanged so as to be able to notify UI that it should refresh/update because the bound model has changed. I believe that there is already a base class that provides that functionality.
Reference Caliburn.Micro.PropertyChangedBase
Update ShellViewModel to be derived from PropertyChangedBase and then in property call one of the available methods that would allow your view model to notify UI of property changed.
public class ShellViewModel : PropertyChangedBase {
private string infoLabel= "something";
public string InfoLabel1 {
get {
return infoLabel;
}
set {
infoLabel = value;
NotifyOfPropertyChange();
//Or
//Set(ref infoLabel, value);
}
}
public void ChangeUserControllButton() {
InfoLabel1 = "Hello world";
}
}
Read more at https://caliburnmicro.com/ to get examples of how to use the framework.

How to Bind to CaretIndex aka curser position of an Textbox

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?

Changing standard property into a DependencyProperty

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

How to bind a dependency property for a custom control to its view models property

I am trying to bind a custom control's dependency property to its ViewModel's property.
The custom control looks like:
public partial class MyCustomControl : Canvas
{
//Dependency Property
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyCustomControl));
private VisualCollection controls;
private TextBox textBox;
public string Text
{
get { return textBox.Text; }
set
{
SetValue(TextProperty, value);
textBox.Text = value;
}
}
//Constructor
public MyCustomControl ()
{
controls = new VisualCollection(this);
InitializeComponent();
textBox = new TextBox();
textBox.ToolTip = "Start typing a value.";
controls.Add(textBox);
//Bind the property
this.SetBinding(TextProperty, new Binding("Text") {Mode = BindingMode.TwoWay, Source = DataContext});
}
}
And the View Model looks like:
-------
public class MyCustomControlViewModel: ObservableObject
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; RaisePropertyChanged("Text");}
}
}
----------
This binding for "Text" Property is not working for some reason.
What I am trying to do is that in the actual implementation I want the text property of MyCustom Control to update when I update the Text property of my underlying ViewModel.
Any help regarding this is much appreciated.
After some research I finally figured out the problem with my code. I got this code working by creating a Static event handler that actually sets the new property value to the underlying public member of the dependency property. The Dependency property declaration is like :
//Dependency Property
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyCustomControl), new PropertyMetadata(null, OnTextChanged));
and then define the static method that sets the property is like :
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyCustomControl myCustomControl = (MyCustomControl)d;
myCustomControl.Text = (string) e.NewValue;
}
this is the only thing I was missing.
Cheers
Just bind to the dependency property
<MyCustomControl Text="{Binding Path=Text}" />
You should bind your member TextBox to your TextProperty instead. I am pretty sure that the binding in xaml on your Text property overrides the one you make in the constructor.

Categories

Resources