Apologies in advance if this has already been asked; I have spent a while Googling and searching stack overflow - but can't find a similar question.
I have a WPF window which has many, many data entry controls. All the controls have two way bindings to the view model, and validate using IDataErrorInfo.
An example of one the bindings is given here:
<TextBox >
<TextBox.Text>
<Binding Path="Street" Mode="TwoWay" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The only difference between this binding an all the others is the Path.
All my bindings require the validation rule, and the instruction about when to update as there is a lot of cross field validation going on.
My question is - can I apply the same binding to a textbox without all the copy/pasting I am currently having to do for the above example?
I was thinking that maybe I should roll my own subclass of binding to take care of it - but I have no idea if this is good practice or not?
Update:
I've just tried a subclass of the binding like so:
public class ExceptionValidationBinding : Binding
{
public ExceptionValidationBinding()
{
Mode = BindingMode.TwoWay;
NotifyOnValidationError = true;
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ValidatesOnDataErrors = true;
ValidationRules.Add(new ExceptionValidationRule());
}
}
which makes my xaml look like this:
<TextBox Text="{bindings:ExceptionValidationBinding Path=PostCode}" />
And it seems to work... like I said - no idea if there are any problems with this approach though.
If you don't want any code in your view's code-behind, you can create a MarkupExtension and use it in your XAML. Here's an example of the MarkupExtension:
public class MyBinding : MarkupExtension
{
public string ThePath { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding binding = new Binding(ThePath) {
Mode = BindingMode.TwoWay,
NotifyOnValidationError = true,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
ValidatesOnDataErrors = true
};
binding.ValidationRules.Add(new ExceptionValidationRule());
return binding;
}
}
Then you can do this in your XAML:
<TextBox Text="{local:MyBinding ThePath=Street}">
Edit:Looks like this gives a runtime error. Change the line return binding; to return binding.ProvideValue(serviceProvider);. I guess deriving a class from Binding is actually better, but I will leave this here anyway :)
Even if you're using MVVM, you can apply bindings from the code-behind (as it is still pure-view logic). "Templating" bindings from within XAML, as far as I know, is not possible.
To do it from your code-behind, like so:
void InitBinding() {
Address bindingSource = ...
String[] paths = new String[] { "Street", "City", etc... };
foreach(TextBox textBox in ...) {
String path = ... // get the path for this textbox
Binding binding = new Binding( path );
binding.Source = bindingSource;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding( textBox, TextBox.TextProperty, binding );
}
}
For posterity - the answer I've gone for was my own first update.
This is because there appear to be no side effects to subclassing a binding, and this lets me over-ride any defaults I set on the binding using straight XAML which is something I can't do (without a hell of a lot of work) using the mark up extension idea.
Thank you all who took the time to help me out with this.
My Answer
I've just tried a subclass of the binding like so:
public class ExceptionValidationBinding : Binding
{
public ExceptionValidationBinding()
{
Mode = BindingMode.TwoWay;
NotifyOnValidationError = true;
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ValidatesOnDataErrors = true;
ValidationRules.Add(new ExceptionValidationRule());
}
}
which makes my xaml look like this:
<TextBox Text="{bindings:ExceptionValidationBinding Path=PostCode}" />
And it seems to work...
Related
Basically, I want to bind a textbox in my xaml with a variable, DisplayNumFilter, in my c#. I would like to initialize my textbox to 20. I've been looking through several stack overflow posts and have tried many things, which is the constructor is kind of a mess (I tried many things and just sort of left them there). However, nothing's worked. My apologies with any mistakes in regards to formatting or terminology, I am still very new to this.
Here is a snippet of my xaml:
<TextBox Name = "NumAccounts"
Text="{Binding Path = DisplayNumFilter, Mode=TwoWay}" />
Here is a snippet of my c# code:
private string _displayNumFilter;
public string DisplayNumFilter{
get => _displayNimFilter;
set{
_displayNumFilter = value;
OnPropertyChanged("DisplayNumFilter");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName){
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public constructor(){ //it has a different name, I just use this as an example
DisplayNumFilter = "20";
InitializeComponent();
Binding binding = new Binding();
binding.Path = new PropertyPath("DisplayNumFilter");
binding.Source = NumAccounts;
BindingOperations.SetBinding(NumAccounts, TextBox.TextPoperty, binding);
NumAccounts.Text = DisplayNumFilter;
}
The XAML markup Text="{Binding Path=DisplayNumFilter}" tries to bind to a DisplayNumFilter of the current DataContext of the TextBox control so you need to set the DataContext to an instance of the class where the DisplayNumFilter is defined.
This means that your constructor should look something like this:
public constructor() {
DisplayNumFilter = "20";
InitializeComponent();
DataContext = this;
}
There is no reason to create a Binding object programmatically if you use setup the bindings in the XAML markup.
There are some issues with your code, but I will focus on the main:
Your binding source is wrong. From that the binding source it will start with the property path. To resolve the property DisplayNumFilter the source has to be set to this.
public SomeWindow()
{
DisplayNumFilter = "20";
InitializeComponent();
Binding binding = new Binding();
binding.Path = new PropertyPath("DisplayNumFilter");
binding.Source = this; // -> was before NumAccounts;
BindingOperations.SetBinding(NumAccounts, TextBox.TextPoperty, binding);
NumAccounts.Text = DisplayNumFilter;
}
For some reason I can't use any binding with a UWP Xaml SwipeItem Control. I've tried it in many different ways and with different SwipeItem properties but every time it is null. What's even stranger is any type of x:Bind to any property and it will crash.
if anyone marks this:
SwipeItem XAML Binding ignored
as a duplicate question it isn't so don't do it or I'll freak out. That question wasn't even answered.
<SwipeControl>
<SwipeControl.LeftItems>
<SwipeItems Mode="Execute">
<SwipeItem Text="{Binding}" Background="{StaticResource MIIGreen}" BehaviorOnInvoked="Close"/>
</SwipeItems>
</SwipeControl.LeftItems>
<SwipeControl.RightItems>
<SwipeItems Mode="Execute">
<SwipeItem Background="{StaticResource MIIRed}" BehaviorOnInvoked="Close" Command="{StaticResource CommandEnclosureNotInstalled}" CommandParameter="{Binding}"/>
</SwipeItems>
</SwipeControl.RightItems>
</SwipeControl>
the DataContext is just a simple DataModel and all other controls are binding fine. the command is from a staticresource and the command is firing just fine. in this example any combination of Binding or x:Bind either does nothing or crashes when trying to bind ANYTHING to Text or CommandParamter properties. There has to be something wrong with SwipItem controls, I need a way to pass the DataContext through the CommandParameter.
SwipeControl is not a standard itemControl, it does not have a dataTemplate, so SwipeItem can't find the DataContext of the parent view, so you can't directly use Binding directly in xaml. It seems you can only use Binding in code.(Below I give example of LeftItems).
in xaml:
<SwipeControl Margin="50" LeftItems="{Binding leftItems}">
</SwipeControl>
in cs:
public SwipeItems leftItems { get; set; }
public MainPage()
{
this.InitializeComponent();
SwipeItem leftItem = new SwipeItem();
Binding myBinding = new Binding();
myBinding.Source = viewmodel;
myBinding.Path = new PropertyPath("MyText"); //the property in viewmodel
BindingOperations.SetBinding(leftItem, SwipeItem.CommandParameterProperty, myBinding);
BindingOperations.SetBinding(leftItem, SwipeItem.TextProperty, myBinding);
Binding commandBinding = new Binding();
commandBinding.Source = new FavoriteCommand(); //command class
BindingOperations.SetBinding(leftItem, SwipeItem.CommandProperty, commandBinding);
leftItems = new SwipeItems() {
leftItem
};
this.DataContext = this;
}
I've got an issue where if an item is selected in a list I want it to update my items in my grid. The binding is done by:
<ScrollViewer Grid.Row="1">
<ItemsControl x:Name="RightGridItemsControl" ItemsSource="{Binding News}" ItemTemplate="{StaticResource RightGridTemplate}"/>
</ScrollViewer>
When an item, e.g. Planet is selected, I want to update the ItemsSource binding to a new list. This is specified in my DataModel.
How can I update this programmatically? I've tried something like this, but it requires a DependencyObject and can't find out what it means. This also looks like WPF rather than UWP.
`var myBinding = new Binding
{
Source = Planets,
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
BindingOperations.SetBinding(new , ItemsControl.ItemsSourceProperty, myBinding);`
What should I put as the first item for the contstructor for 'SetBinding'?
You can set the Binding like this:
BindingOperations.SetBinding(
RightGridItemsControl, ItemsControl.ItemsSourceProperty, myBinding);
or like this:
RightGridItemsControl.SetBinding(ItemsControl.ItemsSourceProperty, myBinding);
Note also that currently there is no property path present in your Binding. If there is a News property as in your XAML, the Binding should probably look like shown below, without Mode = BindingMode.OneWay, which is the default, and without UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, which has no effect in a one-way binding.
var myBinding = new Binding
{
Source = Planets,
Path = new PropertyPath("News")
};
I have a textblock that is a 'status label'. I want this label to be updated, and when that happens, I want its color to be also set automatically - as well as visibility (Label invisible until it has content).
The problem is, that if I specify anything more than the text binding, then the textblock does not change (i.e. text does not appear, and it is still hidden).
Actually, I tried also without binding visibility, and it appears that the Foreground also blocks the binding.
<TextBlock x:Name="StatusInfo"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
FontSize="9"
Visibility="{Binding ElementName=ThisUc,
Path=StatusLabelVisibility}"
Text="{Binding ElementName=ThisUc,
Path=StatusLabel}"
Foreground="{Binding ElementName=ThisUc,
Path=StatusLabelBrush}" />
This is all in a UserControl, so I am using dependency properties for StatusLabel property, as I want to bind it to properties in main window... Foreground and Visibility properties are not dependency properties, as I don't want to expose them.
This is my property setter and getter:
public string StatusLabel
{
get { return (string)GetValue(StatusLabelProperty); }
set
{
SetValue(StatusLabelProperty, value);
RaisePropertyChanged("StatusLabel");
if (value != string.Empty)
{
StatusLabelVisibility = System.Windows.Visibility.Visible;
if (value.HasAny("success", "ok") && !value.HasAny("partial"))
{
StatusLabelBrush = Brushes.Green;
}
else if (value.HasAny("fail"))
{
StatusLabelBrush = Brushes.DarkRed;
}
else if (value.HasAny("partial"))
{
StatusLabelBrush = Brushes.DarkGoldenrod;
}
else
{
StatusLabelBrush = Brushes.Black;
}
}
else
{
StatusLabelVisibility = System.Windows.Visibility.Collapsed;
}
}
}
Please let me know what am I doing wrong, perhaps that's not the way to go altogether?
Cheers
====================
While Meredith's answer solved the issue, let me just post a comment for future reference (as it was not obvious for me):
Here it goes - if you assign the UserControl property directly, not via property binding, it appears to lose the 'bound' - and if you try to change the bound property again, it won't update the control as it would have before it 'lost the bound'
Cheers
If StatusLabel is a DependencyProperty, you can't put anything else in the setter - it won't get called correctly. Look up the way to do changed events for DependencyProperties instead. You need a PropertyChangedCallback. Check out How to use PropertyChangedCallBack. Raise your prop changes, and set all the other properties in the callback.
I'm having a problem with the selecion of an item bound to the selected item of a WPF ListBox from code behind (I'm using the mvvm pattern). The weird thing is that it used to work, but now it doesn't anymore. Let me introduce the situation.
This is the ListBox inside my User Cotrol
<ListBox ItemsSource="{Binding TrainerClassesList}"
DisplayMemberPath="Description"
SelectedItem="{Binding SelectedTrainerClass, Mode=TwoWay}" />
This is the property in the View Model to which the selected item is bound:
public TrainerClassClientEntity SelectedTrainerClass
{
get
{
return selectedTrainerClass;
}
set
{
if (EditingTrainerClass != null && !EditingTrainerClass.Equals(SelectedTrainerClass) && !EditingTrainerClass.Equals(value) && BtnSave.CanExecute(null))
{
MessageBoxResult result = MessageBox.Show("Unsaved changes will be lost. Do you want to save?",
"Attention",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.Yes);
if (result == MessageBoxResult.Yes)
{
BtnSave.Execute(null);
}
}
selectedTrainerClass = value;
EditingTrainerClass = Mapper.Clone<TrainerClassClientEntity>(selectedTrainerClass);
Raise(() => SelectedTrainerClass);
}
}
Basically what happens is that when you make changes to the textboxes of the user control etc, you don't directly change the selected item. You modify a copy of the selected item, and once you press the save button (not shown here), the changes are applied.
What I achieved days ago was a simple button that added an item to the list, and selected it. It used to work, but now it doesn't anymore. This is the action of the button (I used ICommand interface to do that, I'm not going to bother copying and pasting the whole piece of code).
private void AddTrainerClass()
{
TrainerClassClientEntity trainerClass = new TrainerClassClientEntity();
ViewModel.TrainerClassesList.Add(trainerClass);
ViewModel.SelectedTrainerClass = trainerClass;
}
Now, I don't exactly know what I did to make it not work anymore. It does work, via debugging I learnt that it does change the property in the View Model, but it seems like it won't update the user interface.
Recently I added some validators to the textboxes, could it be the problem? Here's an example:
<TextBox Grid.Row="0" Grid.Column="1" Height="25" VerticalAlignment="Center" Margin ="10 5" >
<TextBox.Text>
<Binding Path="EditingTrainerClass.Description"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
<Binding.ValidationRules>
<validators:StringNotNullOrWhiteSpaceValidationRule
ValidatesOnTargetUpdated="True"
ErrorMessage="The field cannot be empty."/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Also, I think it's worth to mention that I'm using a custom WPF Theme. However, it still used to work while I was using it.
Thank in advance for your help.
You have a method that you say is supposed to apply the changes [to the selected item], but unless you have omitted some code from this method, it doesn't appear to apply any changes... surely you need to copy the values from the current selected item to the new one? Either something like this:
private void AddTrainerClass()
{
TrainerClassClientEntity trainerClass = new TrainerClassClientEntity(
ViewModel.SelectedTrainerClass);
ViewModel.TrainerClassesList.Add(trainerClass);
ViewModel.SelectedTrainerClass = trainerClass;
}
Or something like this:
private void AddTrainerClass()
{
TrainerClassClientEntity trainerClass = new TrainerClassClientEntity();
trainerClass.CopyValuesFrom(ViewModel.SelectedTrainerClass);
ViewModel.TrainerClassesList.Add(trainerClass);
ViewModel.SelectedTrainerClass = trainerClass;
}