Wpf Textbox UpdateSourceTrigger doesn't update the source - c#

I have a simple Window with a TextBox
XAML
<Window x:Class="Configurator.ConfiguratorWindow"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</Window>
in the code behind
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata());
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set {
SetValue(DescriptionProperty, value);
_actual_monitor.Description = value;
}
}
}
the graphic is updating right, but when i change the text in the textbox and lose focus it doesn't update the source property.
What is wrong?

DependencyProperties are used for UserControls rather than ViewModel type bindings.
You should
Create a ConfigurationWindowViewModel (Read about MVVM) and implement INotifyPropertyChanged
Create a Property Description that utilizes the INotifyPropertyChanged
Create a new instance of that view model to be set to the DataContext of your ConfigurationWindow.

The getter and setter of the CLR wrapper of a dependency property must not contain any other code than GetValue and SetValue. The reason is explained in the XAML Loading and Dependency Properties article on MSDN.
So remove the _actual_monitor.Description = value; assignment from the setter and add a PropertyChangedCallback to react on property value changes:
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(ConfiguratorWindow),
new PropertyMetadata(DescriptionPropertyChanged));
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
private static void DescriptionPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ConfiguratorWindow obj = d as ConfiguratorWindow;
obj._actual_monitor.Text = (string)e.newValue;
}
}

Try this
<Window x:Class="Configurator.ConfiguratorWindow"
xmlns:myWindow="clr-namespace:YourNamespace"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type myWindow}}, Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata(null, CallBack);
private static void callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var foo = d as ConfiguratorWindow ;
all you need to do, you can do here
}
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set { SetValue(DescriptionProperty, value);}
}
}
But it would be much easier to just have a View Model and bind to property there.

Related

SelectedValue binding of a nested DataGrid doesn't work while ItemsSource does

I have a DataGrid inside of a UserControl which in turn lies inside of another UserControl. This is due to other needs of the project and I can't change this nested architecture. I'm binding a list of Person class to this DataGrid. This is a dumbed-down version without using a VM, but in my real project I am using a VM.
My UserControl with the DataGrid:
<Grid>
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"
MouseDoubleClick="MyDg_MouseDoubleClick"
SelectedValue="{Binding SelectedValue, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Code Behind:
public partial class UCDataGrid : UserControl
{
public event RoutedEventHandler RoutedDataGridDoubleClick;
public UCDataGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
private void MyDg_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
RoutedDataGridDoubleClick?.Invoke(this, new RoutedEventArgs());
}
}
2nd UserControl that contains the above control:
<Grid>
<ContentControl Content="{Binding MyDataGrid, ElementName=ucDisplay}"/>
</Grid>
ucDisplay is simply the Name property value of this UserControl.
Code Behind:
Nothing fancy here.
public partial class UCDisplay : UserControl
{
public UCDisplay()
{
InitializeComponent();
}
public static readonly DependencyProperty MyDataGridProperty = DependencyProperty.Register("MyDataGrid", typeof(object), typeof(UCDisplay), new PropertyMetadata(null));
public object MyDataGrid
{
get { return GetValue(MyDataGridProperty); }
set { SetValue(MyDataGridProperty, value); }
}
}
Main Window
In my Main Window, I bind my People list as well as SelectedPerson instance, like so:
<Grid>
<local:UCDisplay>
<local:UCDisplay.MyDataGrid>
<local:UCDataGrid ItemsSource="{Binding People}"
SelectedValue="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged}"
RoutedDataGridDoubleClick="UCDataGrid_RoutedDataGridDoubleClick"/>
</local:UCDisplay.MyDataGrid>
</local:UCDisplay>
</Grid>
Code Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private List<Person> people;
public List<Person> People
{
get => people;
set => SetField(ref people, value);
}
private Person selectedPerson;
public Person SelectedPerson
{
get => selectedPerson;
set => SetField(ref selectedPerson, value);
}
public MainWindow()
{
InitializeComponent();
People = GetPeople();
DataContext = this;
}
private void UCDataGrid_RoutedDataGridDoubleClick(object sender, RoutedEventArgs e)
{
}
private List<Person> GetPeople()
{
return new List<Person>
{
new Person() { Name = "A" },
new Person() { Name = "B" },
new Person() { Name = "C" }
};
}
public class Person
{
public string Name { get; set; }
}
}
Again, in reality I'm using a VM, this is only to keep things simple.
Now when I run this I can display my list content just fine. But when I double-click an item in my DataGrid, in the corresponding in my Main Window code behind, the SelectedPerson remains null, although its binding is identical to the People list. I confirm this by using a break point in the main code behind:
But if I debug and see the value in the code behind of my innermost UserControl, you see that the SelectedValue there has the correct selected items value.
So what am I doing wrong here? Why can't I seem to bind the SelectedValue although I do it exactly the same as my ItemsSource binding, but the latter works?
SelectedValue is supposed to be used in conjunction with SelectedValuePath. You should use SelectedItem instead.
Besides that, you are missing a TwoWay Binding. Either explicitly declare the SelectedItem Binding TwoWay
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem,
RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}"/>
or register the property to bind TwoWay by default:
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem), typeof(object), typeof(UCDataGrid),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Also note that setting UpdateSourceTrigger=PropertyChanged is pointless in all your Bindings.

How to Create a UWP Reusable Content Dialog That is MVVM Compliant

I am working on a reusable content dialog UserControl that the user can import into their xaml and only have to be responsible for bindings in their respective ViewModel. My UserControl has DependencyProperties tied to appropriate content, but the Content Dialog event's are not visible (programmatically) to users of the UserControl. Specifically, I am looking for .ShowAsync()., but Intellisense does not see .ShowAsync() as being part of UserControl.
I used the examples here as a pattern, but I don't need to extend the DialogContent class for my purpose (I think?): https://learn.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Controls.ContentDialog
As an experiment, I tried to make this SO work, but I then realized this was sitting on Template 10 framework and Dependency Injection/IoC (I am doing my app in straight C#): UWP ContentDialog Invocation
Here is my UserControl's code behind:
public partial class UserDefinedDialogView : UserControl
{
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("DialogTitle", typeof(string), typeof(UserDefinedDialogView), null);
public static readonly DependencyProperty DialogContentTextProperty =
DependencyProperty.Register("DialogContent", typeof(string), typeof(UserDefinedDialogView), null);
public static readonly DependencyProperty PrimaryButtonTextProperty =
DependencyProperty.Register("DialogPrimaryButtonText", typeof(string), typeof(UserDefinedDialogView), null);
public static readonly DependencyProperty SecondaryButtonTextProperty =
DependencyProperty.Register("DialogSecondaryButtonText", typeof(string), typeof(UserDefinedDialogView), null);
public static readonly DependencyProperty CloseButtonTextProperty =
DependencyProperty.Register("DialogCloseButtonText", typeof(string), typeof(UserDefinedDialogView), null);
// PropertyWrappers
public string DialogTitle
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public string DialogContent
{
get { return (string)GetValue(DialogContentTextProperty); }
set { SetValue(DialogContentTextProperty, value); }
}
public string DialogPrimaryButtonText
{
get { return (string)GetValue(PrimaryButtonTextProperty); }
set { SetValue(PrimaryButtonTextProperty, value); }
}
public string DialogSecondaryButtonText
{
get { return (string)GetValue(SecondaryButtonTextProperty); }
set { SetValue(SecondaryButtonTextProperty, value); }
}
public string DialogCloseButtonText
{
get { return (string)GetValue(CloseButtonTextProperty); }
set { SetValue(CloseButtonTextProperty, value); }
}
public UserDefinedDialogView()
{
this.InitializeComponent();
}
}
The UserControl's xaml:
<UserControl
x:Class="HHPM_NEXT.Views.Common.UserDefinedDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HHPM_NEXT.Views.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<ContentDialog x:Name="UserDefinedDialog"
Title="{x:Bind DialogTitle, Mode=TwoWay}"
Content="{x:Bind DialogContent, Mode=TwoWay}"
PrimaryButtonText="{x:Bind DialogPrimaryButtonText, Mode=TwoWay}"
CloseButtonText="{x:Bind DialogCloseButtonText, Mode=TwoWay}"
x:FieldModifier="Public">
</ContentDialog>
</UserControl>
Example Implementation (References to Zeroize are in a ViewModel, but I didn't add it because I didn't want this to get too long):
<views1:UserDefinedDialogView x:Name="ConfimationDialog" DialogTitle="{Binding ZeroizeTitle, Mode=TwoWay}" DialogContent="{Binding ZeroizeContent, Mode=TwoWay}"
DialogPrimaryButtonText="{Binding ZeroizeConfirmButtonText, Mode=TwoWay}" DialogCloseButtonText="{Binding ZeroizeCloseButtonText, Mode=TwoWay}"/>

WPF DependencyProperty PropertyChangedCallback Not Being Called

My issue is my OnMatrixPropertyChanged method never gets called. The label, which is bound to the same property, does update so I know binding is happening on the Matrix property.
I have a UserControl that I want to add a DependencyProperty to in order that it can be bound to. My MainWindow looks like this:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<Button
Command="{Binding LoadMatrixCommand}"
Content="Load"
Width="150">
</Button>
<Label
Content="{Binding Matrix.Title}">
</Label>
<controls:MatrixView
Matrix="{Binding Path=Matrix, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</controls:MatrixView>
</StackPanel>
In my MatrixView UserControl code-behind I have the DependencyProperty set as such:
public partial class MatrixView : UserControl
{
public static readonly DependencyProperty MatrixProperty =
DependencyProperty.Register(nameof(Matrix), typeof(Matrix), typeof(MatrixView), new PropertyMetadata(default(Matrix), OnMatrixPropertyChanged));
private static void OnMatrixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Do Something
}
public Matrix Matrix
{
get => (Matrix)GetValue(MatrixProperty);
set => SetValue(MatrixProperty, value);
}
public MatrixView()
{
InitializeComponent();
}
}
I must be missing something very obvious...
EDIT #1: View Models
public class MatrixViewModel : ViewModelBase
{
public MatrixViewModel()
{
}
}
public class MainWindowViewModel : ViewModelBase
{
private IMatrixService _matrixService;
private Matrix _matrix;
public Matrix Matrix
{
get => _matrix;
set
{
_matrix = value;
base.RaisePropertyChanged();
}
}
public ICommand LoadMatrixCommand { get; private set; }
public MainWindowViewModel()
{
LoadMatrixCommand = new RelayCommand(LoadMatrix);
_matrixService = new MatrixService();
}
private void LoadMatrix()
{
var matrixResult = _matrixService.Get(1);
if (matrixResult.Ok)
{
Matrix = matrixResult.Value;
}
}
}
There certainly is something like
<UserControl.DataContext>
<local:MatrixViewModel/>
</UserControl.DataContext>
in the XAML of your UserControl. Remove that, because it prevents that a Binding like
<controls:MatrixView Matrix="{Binding Matrix}" />
looks up the Matrix property in the correct view model instance, i.e. the one inherited from the MainWindow.
UserControls with bindable (i.e. dependency) properties should never set their own DataContext, because doing so breaks any DataContext based bindings of these properties.

How to use a dependency property defined in custom class?

I have a class that define some custom dependency properties for TextBox class:
public class DependencyProperties:FrameworkElement
{
public static readonly DependencyProperty SelectionBeginProperty = DependencyProperty.Register("SelectionBegin", typeof(int), typeof(TextBox),
new UIPropertyMetadata(0, SelectionStartDependencyPropertyChanged));
public static readonly DependencyProperty SelectionLengthProperty = DependencyProperty.Register("SelectionLength", typeof(int), typeof(TextBox),
new UIPropertyMetadata(0, SelectionLengthDependencyPropertyChanged));
public static readonly DependencyProperty ChildrenProperty = DependencyProperty.Register("Children", typeof (string), typeof (TreeView));
static DependencyProperties()
{
}
...
}
And when I try to use these properties in Xaml:
<TextBox Name="TextBox_1735"
SelectionBegin="{Binding TextBox_1735SelectionBegin, UpdateSourceTrigger=PropertyChanged}"
SelectionLength="{Binding TextBox_1735SelectionLength, UpdateSourceTrigger=PropertyChanged}" />
It raise an exception that the property SelectionBegin can't be resolved.
What you should be looking for is Attached Properties as the standard dependency properties have to be declared in the control itself. You also could inherit from TextBox and add your dependency properties in the derived class.
I created a simple class that should look something like with some remarks. By this example you can make yourself the remaining properties.
AttachedProperty
public class DependencyProperties
{
#region Here put your property declaration
public static readonly DependencyProperty SelectionBeginProperty;
public static void SetSelectionBegin(DependencyObject DepObject, int value)
{
DepObject.SetValue(SelectionBeginProperty, value);
}
public static int GetSelectionBegin(DependencyObject DepObject)
{
return (int)DepObject.GetValue(SelectionBeginProperty);
}
#endregion
#region Here in constructor register you property
static DependencyProperties()
{
SelectionBeginProperty = DependencyProperty.RegisterAttached("SelectionBegin", // RegisterAttached
typeof(int), // Type of your property
typeof(DependencyProperties), // Name of your class
new UIPropertyMetadata(0, SelectionStartDependencyPropertyChanged));
}
#endregion
private static void SelectionStartDependencyPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// Some logic
var textBox = sender as TextBox;
if (textBox == null)
{
return;
}
if (e.NewValue is int && ((int)e.NewValue) > 0)
{
textBox.Background = Brushes.Red;
}
}
}
XAML
<Window x:Class="AttachedPropertyHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AttachedPropertyHelp"
Name="MyWindow" Title="MainWindow"
Height="350" Width="525">
<Grid>
<TextBox Name="MyTextBox"
local:DependencyProperties.SelectionBegin="{Binding Path=Width, ElementName=MyWindow}"
Width="100"
Height="30" />
</Grid>
</Window>
For attached dependency property sets the Width of the Window Int type and if it is greater than zero, then the TextBox marked red Background.

Use a binding to set the text property of a textbox in a usercontrol - WPF

I have a user control which contains a textbox and have created a get/set in the usercontrol to get/set the text property of the textbox.
public class OpenFileControl : UserControl
{
StackPanel sp;
public TextBox tb;
public string Text { get { return tb.Text; } set { tb.Text = value; } }
I then want to set this value based on a binding later on -
<gX3UserControls:OpenFileControl Text="{Binding Value}" />
But I get the following exception
A 'Binding' cannot be set on the 'Text' property of type 'OpenFileControl'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
After some investigation It seems Text needs to be a dependency property, but If I do that I cant work out how to pass the value on to the textbox.
How can I fix this.
Consider using something like this.
Control XAML:
<UserControl x:Class="WpfTestBench.OpenFileControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},
Path=Filename, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</UserControl>
Control codebehind:
using System.Windows;
namespace WpfTestBench
{
public partial class OpenFileControl
{
public static readonly DependencyProperty FilenameProperty =
DependencyProperty.Register("Filename", typeof (string), typeof (OpenFileControl));
public OpenFileControl()
{
InitializeComponent();
}
public string Filename
{
get { return (string)GetValue(FilenameProperty); }
set { SetValue(FilenameProperty, value); }
}
}
}
Main XAML:
<Window x:Class="WpfTestBench.OpenFileWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfTestBench="clr-namespace:WpfTestBench"
Title="OpenFileWindow" Width="300" SizeToContent="Height">
<StackPanel>
<wpfTestBench:OpenFileControl x:Name="In" Filename="{Binding SelectedFilename, UpdateSourceTrigger=PropertyChanged}" />
<wpfTestBench:OpenFileControl x:Name="Out" Filename="{Binding ElementName=In, Path=Filename}" />
</StackPanel>
</Window>
Main codebehind:
namespace WpfTestBench
{
public partial class OpenFileWindow
{
public OpenFileWindow()
{
InitializeComponent();
DataContext = this;
}
public string SelectedFilename { get; set; }
}
}
Execution result (after typing something in the first control):
If you define the dependency property as the static and the actual property, you can write whatever code behind you want in the body of the property.
public const string TextPropertyName = "Text";
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
TextPropertyName,
typeof(string),
typeof(MyControl),
new UIPropertyMetadata(false));
In the getter and setter you can do something like textBox1.Text = value; but you'd probably be better served using a binding to the property instead. MVVM frameworks make light work of this sort of thing quite often. You might find more success defining a ViewModel (a class with an appropriate FielPath variable for example) and setting the DataContext of the new UserControl to be an instance of the ViewModel class, using Bindings to do the heavy lifting for you.

Categories