ValidationRule with ValidationStep="UpdatedValue" is called with BindingExpression instead of updated value - wpf

I am getting started with using ValidationRules in my WPF application, but quite confused.
I have the following simple rule:
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (String.IsNullOrWhiteSpace(value as string))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
}
Used in XAML as follows:
<TextBox>
<TextBox.Text>
<Binding Path="Identity.Name">
<Binding.ValidationRules>
<validation:RequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This mostly works as I would expect. I was surprised to see that my source property (Identity.Name) was not being set; I have an undo function that never sees the change, and there is no way to revert the value other than re-type it (not good).
Microsoft's Data Binding Overview describes the validation process near the bottom, which explains this behavior very well. Based on this, I would want to have my ValidationStep set to UpdatedValue.
<validation:RequiredRule ValidationStep="UpdatedValue"/>
This is where things get weird for me. Instead of Validate() being called with object value being the property value that was set (i.e., a string), I get a System.Windows.Data.BindingExpression! I don't see anything in Microsoft's documentation that describes this behavior.
In the debugger, I can see the source object (the DataContext of the TextBox), navigate the path to the property, and see that the value has been set. However, I don't see any good way to get to the right property within the validation rule.
Note: With ValidationStep as ConvertedProposedValue, I get the entered string (I don't have a converter in use), but it also blocks the source property update when validation fails, as expected. With CommittedValue, I get the BindingExpression instead of the string.
There are several questions in here:
Why do I get an inconsistent argument type being passed to Validate() based on the ValidationStep setting?
How can I get to the actual value from the BindingExpression?
Alternately, is there a good way to allow the user to revert the TextBox to the previous (valid) state? (As I mentioned, my own undo function never sees the change.)

I have solved the problem of extracting the value from the BindingExpression, with a minor limitation.
First, some more complete XAML:
<Window x:Class="ValidationRuleTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationRuleTest"
Title="MainWindow" Height="100" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="String 1"/>
<TextBox Grid.Column="1">
<TextBox.Text>
<Binding Path="String1" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="String 2" Grid.Row="1"/>
<TextBox Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="String2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</Window>
Note that the first TextBox uses ValidationStep="RawProposedValue" (the default), while the second one uses ValidationStep="UpdatedValue", but both use the same validation rule.
A simple ViewModel (neglecting INPC and other useful stuff):
class MainWindowViewModel
{
public string String1
{ get; set; }
public string String2
{ get; set; }
}
And finally, the new RequiredRule:
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
// Get and convert the value
string stringValue = GetBoundValue(value) as string;
// Specific ValidationRule implementation...
if (String.IsNullOrWhiteSpace(stringValue))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (Validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
object dataItem = binding.DataItem;
string propertyName = binding.ParentBinding.Path.Path;
// Extract the value of the property.
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
// This is what we want.
return propertyValue;
}
else
{
// ValidationStep was RawProposedValue or ConvertedProposedValue
// The argument is already what we want!
return value;
}
}
}
The GetBoundValue() method will dig out the value I care about if it gets a BindingExpression, or simply kick back the argument if it's not. The real key was finding the "Path", and then using that to get the property and its value.
The limitation: In my original question, my binding had Path="Identity.Name", as I was digging into sub-objects of my ViewModel. This will not work, as the code above expects the path to be directly to a property on the bound object. Fortunately, I have already flattened my ViewModel so this is no longer the case, but a workaround could be to set the control's datacontext to be the sub-object, first.
I'd like to give some credit to Eduardo Brites, as his answer and discussion got me back to digging on this, and did provide a piece to his puzzle. Also, while I was about to ditch the ValidationRules entirely and use IDataErrorInfo instead, I like his suggestion on using them together for different types and complexities of validation.

This is an extension to mbmcavoy's answer.
I have modified the GetBoundValue method in order to remove the limitation for binding paths. The BindingExpression conveniently has the properties ResolvedSource and ResolvedSourcePropertyName, which are visible in the Debugger but not accessible via normal code. To get them via reflection is no problem though and this solution should work with any binding path.
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);
// Extract the value of the property
object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);
return propertyValue;
}
else
{
return value;
}
}

This is an alternative extension to mbmcavoy's and adabyron's answer.
In order to remove the limitation for binding paths, I get the property value using such method:
public static object GetPropertyValue(object obj, string propertyName)
{
foreach (String part in propertyName.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
Now simply change
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
to
object propertyValue = GetPropertyValue(dataItem, propertyName);
Related post: Get property value from string using reflection in C#

In order to answer to your 2 question:
string strVal = (string)((BindingExpression)value).DataItem

Related

Trigger on attached property of DataGridTextColumn

I am trying to define a custom attached property on DataGridTextColumn and writing a DataTrigger against it in my xaml file. Here is how the attached property (FilterDisplayStyle) is defined in my class.
//Dependency Property whether Column Filter is combobox or textbox or editable combobox.
public static FrameworkPropertyMetadata inheritsMetaData =
new FrameworkPropertyMetadata(FilterDisplayTypeEnum.TextBoxOnly, FrameworkPropertyMetadataOptions.Inherits);
public static DependencyProperty FilterDisplayTypeProperty = DependencyProperty.RegisterAttached("FilterDisplayType",
typeof(FilterDisplayTypeEnum), typeof(DataGridColumn), inheritsMetaData);
public static FilterDisplayTypeEnum GetFilterDisplayType(DependencyObject target) {
if (target == null) { throw new ArgumentNullException("Invalid Parameter Element"); }
return (FilterDisplayTypeEnum)target.GetValue(FilterDisplayTypeProperty);
}
public static void SetFilterDisplayType(DependencyObject target, FilterDisplayTypeEnum value) {
if (target == null) { throw new ArgumentNullException("Invalid Parameter Element"); }
target.SetValue(FilterDisplayTypeProperty, value);
}
The above attached property's type is FilterDisplayTypeEnum which is defined as below.
public enum FilterDisplayTypeEnum {
TextBoxOnly,
NonEditableComboBox,
EditableComboBox
}
Here is how I set this property in DataGridTextColumn
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" f:DataGridColumnExtensions.FilterDisplayType="NonEditableComboBox" />
....
</DataGrid.Columns>
Now I am trying to retrieve this property in using the following
<TextBox Text="{Binding Mode=OneWay, Path=FilterDisplayType, RelativeSource={RelativeSource AncestorType={x:Type DataGridTextColumn}}}"/>
But I don't get any text on my TextBox above.
Surprisingly, I have another attached property (this time attached to DataGrid instead) that works perfectly fine. The issue is only with DataGridTextColumn. Also, using WPF Inspector, I see there is no direct visual representation of DataGridTextColumn in the Visual Tree , so I was skeptical whether I could use FindAncestor way of binding on ancestor which is DataGridTextColumn. Can anyone help me out in this scenario. To Summarize, I can't access a custom attached property defined on DataGridTextColumn using FindAncestor type of Binding. Are there any alternatives to this?
regards,
Nirvan
Edit:
As per #Clemens suggestions, I changed the definition of the Attached Property to something like this. But I still can't access the attached property in my xaml.
Attached Property Definition:
public static DependencyProperty FilterDisplayTypeProperty = DependencyProperty.RegisterAttached("FilterDisplayType",
typeof(FilterDisplayTypeEnum), typeof(DataGridColumnExtensions), inheritsMetaData);
public static FilterDisplayTypeEnum GetFilterDisplayType(DataGridBoundColumn target) {
if (target == null) { throw new ArgumentNullException("Invalid Parameter target"); }
return (FilterDisplayTypeEnum)target.GetValue(FilterDisplayTypeProperty);
}
public static void SetFilterDisplayType(DataGridBoundColumn target, FilterDisplayTypeEnum value) {
if (target == null) { throw new ArgumentNullException("Invalid Parameter target"); }
target.SetValue(FilterDisplayTypeProperty, value);
}
I am still unable to access the property "FilterDisplayType" in my xaml code as given below
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridTextColumn}}, Path=FilterDisplayType}"/>
The owner type must be the type that declares the property, here DataGridColumnExtensions:
public static DependencyProperty FilterDisplayTypeProperty =
DependencyProperty.RegisterAttached("FilterDisplayType",
typeof(FilterDisplayTypeEnum),
typeof(DataGridColumnExtensions), // here
inheritsMetaData);
This seems to be a common misunderstanding with attached properties. See also here.
And also note that the syntax for binding to an attached property is (Class.Property), so you would need to bind like this:
<TextBox
Text="{Binding Path=(DataGridColumnExtensions.FilterDisplayType)}"/>
And just another note: i haven't quite understood why the property inherits. As far as i can see you intend to set it explicitly on DataGridTextColumn objects.

UpdateSourceTrigger=Explicit

I'm creating a WPF window with multiple textboxes, when the user presses the OK button I want all the text boxes to be evaluated for being non-blank.
I understand that I have to use TextBoxes with 'UpdateSourceTrigger of 'Explicit', but do I need to call 'UpdateSource()' for each of them ?
e.g.
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="206,108,0,0"
Text="{Binding Path=Definition, UpdateSourceTrigger=Explicit}"
Name="tbDefinitionFolder"
VerticalAlignment="Top"
Width="120" />
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="206,108,0,0"
Text="{Binding Path=Release, UpdateSourceTrigger=Explicit}"
Name="tbReleaseFolder"
VerticalAlignment="Top"
Width="120" />
...
BindingExpression be = tbDefinitionFolder.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
BindingExpression be2 = tbReleaseFolder.GetBindingExpression(TextBox.TextProperty);
be2.UpdateSource();
If you use Explicit you need to call UpdateSource.
I am not sure if this is the best approach to what you try to do though, i for one virtually never use Explicit, i rather bind to a copy of an object if i do not want changes to apply right away, or i store a copy and revert everything back if edits are to be cancelled.
An alternative approach can be to set your UpdateSourceTrigger to PropertyChanged.
And then inherit your VM from both INotifyPropertyChanged and IDataErrorInfo. Here's an example...
public class MyViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string myVar;
public string MyProperty
{
[DebuggerStepThrough]
get { return myVar; }
[DebuggerStepThrough]
set
{
if (value != myVar)
{
myVar = value;
OnPropertyChanged("MyProperty");
}
}
}
private void OnPropertyChanged(string prop)
{
if(PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pro));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { return String.Empty; }
}
public string this[string columnName]
{
get
{
if (columnName == "MyProperty")
{
if (String.IsNullOrEmpty(MyProperty))
{
return "Should not be blank";
}
}
return null;
}
}
}
Assume that one of your TextBoxes is bound to 'MyProperty' as declared above. The indexer is implemented in IDataErrorInfo, and gets called when 'MyProperty' changes. In the indexer body, you can perform a check if the value is empty and return an error string. If the error string is non-null, the user gets a nice adorner on the TextBox as a visual cue. So you are in one shot performing validation and delivering the UI experience.
All of this is for free if you use the two interfaces as coded above and use UpdateSourceTrigger=PropertyChanged. The use of UpdateSourceTrigger = Explicit is massive overkill for providing the validation you described.
The Xaml for the TextBox would be...
<TextBox DataContext="{StaticResource Vm}" Text="{Binding MyProperty,
UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,
NotifyOnSourceUpdated=True, Mode=TwoWay}" Width="200" Height="25"/>
There are some good reasons to use UpdateSourceTrigger=Explicit, instead of other values. Imagine that you have to check if entered value is unique, which will be done by reading database. That can take some time, even 0.3 seconds is unacceptable. When using PropertyChanged, this database checking will be performed every time user presses key, which makes user interface extremely unresponsive. Same thing happens if UpdateSourceTrigger=LostFocus and user will be quickly switching between controls (if you hold Tab, there is lightning fast cycling between controls). So our objective is to validate everything at once at key moment (usually before data is saved). This approach will need minimal code behind, which will push data from view to viewmodel and forces validation. There is no validation code or other application logic inside code behind, so MVVM purists can be relatively calm. I created fully functional example in VB.NET, which is using Caliburn.Micro for MVVM and IoC. You can download it here: https://drive.google.com/file/d/0BzdqT0dfGkO3OW5hcjdBOWNWR2M
I've written some extensions that might make it more comfortable to update sources:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
internal static class EXT_Binding
{
internal static void _UpdateSource(this FrameworkElement element, DependencyProperty dp)
{
BindingExpression be = element.GetBindingExpression(dp: dp);
be?.UpdateSource();
}
internal static void _UpdateSource(this DependencyProperty dp, FrameworkElement element)
{
BindingExpression be = element.GetBindingExpression(dp: dp);
be?.UpdateSource();
}
internal static void _UpdateSources(this FrameworkElement element, List<DependencyProperty> dps)
{
foreach (var dp in dps)
{
BindingExpression be = element.GetBindingExpression(dp: dp);
be?.UpdateSource();
}
}
internal static void _UpdateSources(this FrameworkElement element, params DependencyProperty[] dps)
{
foreach (var dp in dps)
{
BindingExpression be = element.GetBindingExpression(dp: dp);
be?.UpdateSource();
}
}
}
which, for example, can then be used as such
this._UpdateSources(WidthProperty, HeightProperty, LeftProperty, TopProperty);
It would be simpler to just set it to UpdateSourceTrigger=PropertyChanged although it will update the underlying variable each time the value changes (for each letter inputted)

How can I disable a WPF dialogue OK button in response to invalid input determined by a custom ValidationRule class?

I have a simple WPF dialogue allowing the user to enter a name. We are using a Mvvm approach without any code-behind files. I need to validate the input and only enable the OK button when the input is valid. I am currently doing the validation using a custom error template in my view and a custom implementation of the ValidationRule class.
The text box in the dialogue is defined as:
<TextBox Width="250" Height="25" Margin="5"
Validation.ErrorTemplate="{StaticResource customErrorTemplate}">
<TextBox.Text>
<Binding Path="WitnessName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ValidationRules:NameRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The NameRule is defined as:
public class NameRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var isValid = (value as string == null) || Regex.IsMatch(value.ToString(), #"^[\p{L} \.'\-]+$");
return new ValidationResult(isValid, "Name can contain only letters, apostrophes and hyphens.");
}
}
The OK button IsEnabled property is bound to an IsOkEnabled property on the view model, which is only true when the input is valid.
<Button Name="OnOkClick" Margin="5" IsEnabled="{Binding IsOkEnabled}">OK</Button>
The IsOkEnabled property is updated in response to a change in the WitnessName text by monitoring the WitnessName PropertyChanged event.
public string WitnessName
{
get
{
return this.witnessName;
}
set
{
this.witnessName = value;
this.NotifyOfPropertyChange(() => this.IsOkEnabled);
}
}
The problem is that with the validation operating in the view the PropertyChanged event isn't being fired on the vew model when invalid input is entered, so the IsOkEnabled property isn't being updated and the OK button remains enabled.
Is there a way of forcing the update on the IsOkEnabled property in response to a even invalid input on my current implementation?
I have looked at both Karl Shifflet's and Josh Smith's suggestions but neither uses (as far as I can tell) a view error template and I'd like to use one to provide the visual feedback.
Update: Trying this with a bound ICommand as suggested by Danny
I have tried this by creating a VM specific to the OK button, which implements ICommand. The OkButtonViewModel has a property to hold the dialogue VM (set by IoC and unity) and the CanExecute and Execute implementations refer to the relevant properties/methods on this VM.
In OkButtonViewModel:
public bool CanExecute(object parameter)
{
return this.witnessDialogue.IsValid;
}
public void Execute(object parameter)
{
this.witnessDialogue.OnOkClick();
}
How do I bind my button to this VM rather than the dialogueVM? I can do it if the OkButtonViewModel is a property on the dialogue VM but not when the dependence is this other way around, which it needs to be for the button to be able to use the implementations on the dialogue VM.
Get rid of the OnClick event handler, instead use a binding to a command.
Register the command in the VM with a CanExecute method that returns Model.IsValid.
If the validation failure is in the binding of the view to the viewmodel,
then the viewmodel can still be valid - since it didn't store the update (due to type mismatch, range check, etc).
In this case consider storing error state in VM, before throwing exception, then clearing error state if same property was successfully set.
Modified code:
XAML:
(Note that the scope of ValidationRules:NameRule instance is in TextBox, other usages will receive another instance with different field values)
<TextBox Width="250" Height="25" Margin="5"
Validation.ErrorTemplate="{StaticResource customErrorTemplate}">
<TextBox.Text>
<Binding Path="WitnessName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ValidationRules:NameRule x:Name="nameValidator" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Validation rule:
public class NameRule : ValidationRule, INotifyPropertyChanged
{
public bool HasFailed // set default of field behind to false
{
get; // change to support INotifyPropertyChanged
set; // change to support INotifyPropertyChanged
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var isValid = (value as string == null) || Regex.IsMatch(value.ToString(), #"^[\p{L} \.'\-]+$");
HasFailed = !isValid;
return new ValidationResult(isValid, "Name can contain only letters, apostrophes and hyphens.");
}
}
Command:
public bool CanExecute(object parameter)
{
return this.witnessDialogue.IsValid && !this.witnessDialogue.nameValidator.HasFailed;
}
public void Execute(object parameter)
{
this.witnessDialogue.OnOkClick();
}
I know this is a really old question but for those that are lead here by Google like I was...
The best way (that I know of) to do this in WPF with MVVM is to use IDataErrorInfo interface on your view model where the command definition is. This lets you validate and have the validation info in your view model to work with on your CanExecute implementation. I found a good thorough description of using this at https://codeblitz.wordpress.com/2009/05/08/wpf-validation-made-easy-with-idataerrorinfo/ (did not write that, just a useful link I found).
Hope that helps someone!

WPF, multibinding a CSV to a listbox of checkboxes, and MVVM best practices

I'm relatively new to the whole WPF and MVVM idea and I'm looking for advice on a best practice. I have a solution that works but it feels like I might be missing some great XAML syntax that would simplify the whole thing.
I have a string field in a database table that is stored as a CSV, e.g. "CAT, DOG". Perhaps I should have done this as a many-to-many relationship in my entity data model, but that is a different best practice discussion.
In my XAML, I am using a multibinding on a ListBox that contains CheckBoxes. The domain of possible choices is determined at runtime and the ListBox generates CheckBoxes using a DataTemplate. Here's the XAML:
<ListBox Grid.Column="3" Grid.Row="8" Grid.RowSpan="2" Name="brandedProductsListBox" Margin="3" ItemsSource="{Binding Source={StaticResource brandedProductLookup}}" IsSynchronizedWithCurrentItem="True" TabIndex="475">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="3" Content="{Binding Path=BrandedProductName}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource brandedProductToBoolean}">
<Binding Source="{StaticResource projectsView}" Path="BrandedProducts" />
<Binding Path="BrandedProductName" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I use a converter to check the appropriate CheckBoxes. I tried to get the ConvertBack method of the converter to turn a boolean into my CSV string, but I couldn't figure out how to get access which BrandedProductName when all I was passed was a boolean. Here's the converter:
public class BrandedProductToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) {
return false;
}
else {
// The bindings passed in (in order) are: the BrandedProducts field for the current project,
// and the Branded Product represented by the current CheckBox.
string brandedProducts = value[0] as string;
string brandedProduct = value[1] as string;
return brandedProducts == null ? false : brandedProducts.Contains(brandedProduct);
}
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
}
So Convert properly checks the right CheckBoxes when an entity is selected, but when adding a new one I figured out I could use the Checked and UnChecked event handlers of the CheckBox to write back to my entity, like so:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts == null) {
project.BrandedProducts = (string)checkBox.Content;
}
else {
project.BrandedProducts += ", " + (string)checkBox.Content;
}
}
e.Handled = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts != null) {
project.BrandedProducts = project.BrandedProducts.Replace((string)checkBox.Content + ", ", "").Replace(", " + (string)checkBox.Content, "");
}
}
e.Handled = true;
}
If you're still with me, the question is what is a better way to do this? It feels a bit like apples and oranges with me using a converter to generate the view from the entity but then using event handlers to translate view updates/commands back to the entity. Does it violate some goal of MVVM to use event handlers to modify my ViewModel this way?
Thanks in advance for any suggestions,
Ray
Ray,
If you're still with me, the question
is what is a better way to do this?
I've found that with WPF, if you're asking this question, there probably is a better way. There are just so many options (compared to wimpy WinForms).
Does it violate some goal of MVVM to
use event handlers to modify my
ViewModel this way?
IMHO, yes, it does violate MVVM. You should have no ViewModel code (besides setting up a ViewModel Binding) in your code-behind.
You should have your events execute ICommand(s) which are exposed by your ViewModel (i.e. Add, Remove). See the EventToCommand which would probably apply to your CheckBox_Checked and CheckBox_UnChecked events.
-jberger

Setting XAML property value to user control

I have a user control in WPF which i want the text of one of it's labels to be read from the XAML where it is used. Hence..
My User Control:
<UserControl x:Class="muc">
<Label Foreground="#FF7800" FontSize="20" FontWeight="Bold">
<Label.Content>
<Binding ElementName="TestName" Path="." />
</Label.Content>
</Label>
</UserControl>
Then using it:
<mycontorls:muc TestName="This is a test" />
But it doesn't works ...
How can i read the properties ?
I tried the first two answers and what I got worked in code but not on XAML (also doesn't let you see changes in the design view when using the control).
To get a property working like any other native one, here is the full process:
(The sample adds a dependency property of type Nullable to show in the control as text or a default if null)
In the code file:
1.a Define a dependency property:
public static readonly DependencyProperty MyNumberProperty = DependencyProperty.Register("MyNumber", typeof(Nullable<int>), typeof(MyUserControl), new PropertyMetadata(null, new PropertyChangedCallback(OnMyNumberChanged)));
1.b Implement the OnMyNumberChanged Callback:
private static void OnMyNumberChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args){
// When the color changes, set the icon color PlayButton
MyUserControl muc = (MyUserControl)obj;
Nullable<int> value = (Nullable<int>)args.NewValue;
if (value != null)
{
muc.MyNumberTextBlock.Text = value.ToString();
}
else
{
muc.MyNumberTextBlock.Text = "N/A";
}
}
1.c implement the MyNumber property (not dependency) to use the dependency property for easy in code use:
public Nullable<int> MyNumber{
get
{
return (Nullable<int>)GetValue(MyNumberProperty);
}
set
{
SetValue(MyNumberProperty, value);
OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value)); // Old value irrelevant.
}
}
In the XAML file bind the TextBlock control's text to the property (not dependency) to get the default value of the dependency property in case it is not set by the user of the control (assuming you called your root element of the user control "RootElement"):
This code:
< TextBlock Name="MyNumberTextBlock" Text="{Binding MyNumber, ElementName=RootElement}"/>
If you give the root UserControl element a name, then you can refer to it using ElementName:
<UserControl x:Class="muc"
Name="rootElement">
<Label Foreground="#FF7800" FontSize="20" FontWeight="Bold">
<Label.Content>
<Binding ElementName="rootElement" Path="TestName" />
</Label.Content>
</Label>
</UserControl>
You can also use the markup extension syntax to make it a little shorter:
<UserControl x:Class="muc"
Name="rootElement">
<Label Foreground="#FF7800" FontSize="20" FontWeight="Bold"
Content="{Binding TestName, ElementName=rootElement}"/>
</UserControl>
Also remember that your control will be created before its properties are set. You will either need to implement INotifyPropertyChanged or have TestName be a dependency property so that the binding is re-evaluated after the property is set.
I've only done this with Silverlight, but i wouldnt be surprised if it works in the exact same way!
// <summary>
// Xaml exposed TextExposedInXaml property.
// </summary>
public static readonly DependencyProperty TestNameProperty = DependencyProperty.Register("TestName", typeof(string), typeof(NameOfMyUserControl), new PropertyMetadata(string.empty));
// <summary>
// Gets or sets the control's text
// </summary>
public string TextExposedInXaml
{
get
{
return (string)GetValue(TestNameProperty );
}
set
{
SetValue(TestNameProperty , value);
// set the value of the control's text here...!
}
}
{Binding ElementName=x} binds to an element with name x in the element tree, there is nothing here that deals with property TestName. If you want a property on your user control, then you have to define the property in the class corresponding to that user control (in your case it would be muc), and use {Binding RelativeSource={RelativeSource FindAncestor, ...}} to reference it on your user control (see here for details), or give it a name so you can use ElementName.

Resources