After done with setting up MVVM Light in a Universal Windows App application, I have the following structure, and I wonder what is the cleanest way to do validation in 2017 using UWP and mvvmlight to notify users with errors and possibly reset the textbox value when needed. The only trick is that the Textbox is part of a UserControl (cleaned up unnecessary xaml code for clarity) since it will be used multiple times. Also I added DataAnnotations and ValidationResult for demonstration and not to suggest that this is the best way to do it or that it is working in any way so far.
The code works fine as far as binding and adding and removing values
ViewModel
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views;
using System;
using System.ComponentModel.DataAnnotations;
public class ValidationTestViewModel : ViewModelBase
{
private int _age;
[Required(ErrorMessage = "Age is required")]
[Range(1, 100, ErrorMessage = "Age should be between 1 to 100")]
[CustomValidation(typeof(int), "ValidateAge")]
public int Age
{
get { return _age; }
set
{
if ((value > 1) && (value =< 100))
_age= value;
}
}
public static ValidationResult ValidateAge(object value, ValidationContext validationContext)
{
return null;
}
}
View
<Page
x:Class="ValidationTest.Views.ValidationTestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ValidationTest.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding ValidationTestPageInstance, Source={StaticResource Locator}}"
xmlns:views="using:ValidationTest.Views">
<views:NumberEdit TextInControl="{Binding Age, Mode=TwoWay}" />
</Page>
UserControl
<UserControl
x:Class="ValidationTest.Views.Number"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ValidationTest.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="userControl1">
<Grid>
<TextBox x:Name="content" Text="{Binding TextInControl, ElementName=userControl1, Mode=TwoWay}"></TextBox>
</Grid>
</UserControl>
UserControl Code Behind:
public partial class NumberEdit : UserControl
{
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set {
SetValue(TextInControlProperty, value);
}
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string),
typeof(NumberEdit), new PropertyMetadata(null));
}
We usually use IDialogService interface in MVVM Light to notify users with errors, there are ShowError method, ShowMessage method and ShowMessageBox method in IDialogService.
We should be able to new a PropertyMetadata instance with a PropertyChangedCallback value, it will be invoked when the effective property value of a dependency property changes. When it is invoked, we can use the ShowMessage method in it.
For example:
public sealed partial class NumberEdit : UserControl
{
public NumberEdit()
{
this.InitializeComponent();
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(string),
typeof(NumberEdit), new PropertyMetadata(null, new PropertyChangedCallback(StringChanged)));
private static void StringChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
int value;
Int32.TryParse(e.NewValue.ToString(), out value);
if (0 < value && value < 99)
{
}
else
{
var dialog = ServiceLocator.Current.GetInstance<IDialogService>();
dialog.ShowMessage("Age should be between 1 to 100", "Error mesage");
}
}
public string TextInControl
{
get { return (string)GetValue(TextInControlProperty); }
set
{
SetValue(TextInControlProperty, value);
}
}
}
Also if you want to reset the TextBox value, you should be able to use RaisePropertyChanged in the Age property. If we do not use RaisePropertyChanged in the Age property, the TextBox value will not change when the Age value has changed.
For more info about the RaisePropertyChanged, please refer INotifyPropertyChanged interface.
For example:
public int Age
{
get { return _age; }
set
{
if ((value > 1) && (value <= 100))
_age = value;
RaisePropertyChanged("Age");
}
}
Update:
In your Page you should be add to add DataContext in your control.
<Page x:Class="Validation_Using_MVVM_Light_in_a.SecondPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="using:Validation_Using_MVVM_Light_in_a"
xmlns:VM="using:Validation_Using_MVVM_Light_in_a.ViewModel">
<Page.Resources>
<VM:ValidationTestViewModel x:Key="MyViewModel" />
</Page.Resources>
<Grid>
<local:NumberEdit DataContext="{StaticResource MyViewModel}" TextInControl="{Binding Age, Mode=TwoWay}" />
</Grid>
</Page>
What you are missing here is a call to Validator.ValidateObject to do the actual validation. This will apply the validation attributes to the data and will also call IValidatableObject.Validate if you have implemented it (you should implement this instead of having ad-hoc functions such as ValidateAge).
Like this:
// Validate using:
// 1. ValidationAttributes attached to this validatable's class, and
// 2. ValidationAttributes attached to the properties of this validatable's class, and
// 3. this.Validate( validationContext)
//
// Note, for entities, a NotSupportedException will be thrown by TryValidateObject if any of
// the primary key fields are changed. Correspondingly the UI should not allow modifying
// primary key fields.
ValidationContext validationContext = new ValidationContext(this);
List<ValidationResult> validationResults = new List<ValidationResult>(64);
bool isValid = Validator.TryValidateObject( this, validationContext, validationResults, true);
Debug.Assert(isValid == (validationResults.Count == 0));
Related
I've got a problem with my WPF UserControl binding one property in multiple controls and back. The setters of the business object will not be called. I searched for hours now and tried serveral things which did not work.
My Code
Can be download here: WpfApplicationUserControlProblem.zip
My Business object has 2 DateTime Values to be bound.
public class BusinessObject
{
private DateTime _value1 = DateTime.Today.AddHours(10);
public DateTime Value1
{
get { return _value1; }
set { _value1 = value; } // will never be called but why??
}
private DateTime _value2 = DateTime.Today.AddDays(1).AddHours(15);
public DateTime Value2
{
get { return _value2; }
set { _value2 = value; } // will never be called but why??
}
}
My Main-Window has 2 user controls to bind the 2 values of my object
<Window x:Class="WpfApplicationUserControlProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplicationUserControlProblem"
mc:Ignorable="d"
Title="MainWindow" Height="120.961" Width="274.489">
<Grid>
<local:DateTimeUserControl DateTimeValue="{Binding Value1, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="241"/>
<local:DateTimeUserControl DateTimeValue="{Binding Value2, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,39,0,0" VerticalAlignment="Top" Width="241"/>
</Grid>
public partial class MainWindow : Window
{
private BusinessObject _businessObject = new BusinessObject();
public MainWindow()
{
InitializeComponent();
DataContext = _businessObject;
}
}
My UserControl DateTimeUserControl has one DependencyProperty "DateTimeValue" for receiving the bound business value from the Main-Window. With the "DateTimeValuePropertyChangedCallback" I redirect the received value into a DateValue for the DatePicker and HourValue for the HourTextBox. Changing the DatePicker or HourTextBox should update the DependencyProperty "DateTimeValue" and therefore also the bounded business object. That was my plan.
<UserControl x:Class="WpfApplicationUserControlProblem.DateTimeUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplicationUserControlProblem"
x:Name="_this"
mc:Ignorable="d">
<Grid>
<DatePicker SelectedDate="{Binding Path=DateValue, ElementName=_this, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Margin="0,0,61,0"/>
<TextBox Text="{Binding Path=HourValue, ElementName=_this, UpdateSourceTrigger=PropertyChanged}" Height="24" VerticalAlignment="Top" HorizontalAlignment="Right" Width="56"/>
</Grid>
public partial class DateTimeUserControl : UserControl, INotifyPropertyChanged
{
public DateTimeUserControl()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty DateTimeValueProperty = DependencyProperty.Register(nameof(DateTimeValue), typeof(DateTime), typeof(DateTimeUserControl), new PropertyMetadata(DateTimeValuePropertyChangedCallback));
public DateTime DateTimeValue
{
get { return (DateTime)GetValue(DateTimeValueProperty); }
set { SetValue(DateTimeValueProperty, value); }
}
private static void DateTimeValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DateTimeUserControl control = d as DateTimeUserControl;
control.FirePropertyChanged(d, new PropertyChangedEventArgs(nameof(DateValue)));
control.FirePropertyChanged(d, new PropertyChangedEventArgs(nameof(HourValue)));
}
private void FirePropertyChanged(object sender, PropertyChangedEventArgs args)
{
PropertyChanged?.Invoke(sender, args);
}
public DateTime DateValue
{
get { return DateTimeValue.Date; }
set { DateTimeValue = value.Date.AddHours(DateTimeValue.Hour); }
}
public string HourValue
{
get { return DateTimeValue.Hour.ToString(); }
set { DateTimeValue = DateTimeValue.Date.AddHours(int.Parse(value)); }
}
}
I don't get it
Everything seems to work fine except that the setter of the business object is not called when the DependencyProperty is updated. But why? I also tried everything with DependencyProperties or MultiBindingConverters. I failed on every try.
Can anybody help?
The DateTimeValue Bindings should be declared as TwoWay, while UpdateSourceTrigger=PropertyChanged is certainly redundant:
<local:DateTimeUserControl DateTimeValue="{Binding Value1, Mode=TwoWay}" .../>
You could also declare your DateTimeValue dependency property to bind two-way by default:
public static readonly DependencyProperty DateTimeValueProperty =
DependencyProperty.Register(
nameof(DateTimeValue),
typeof(DateTime),
typeof(DateTimeUserControl),
new FrameworkPropertyMetadata(
default(DateTime),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
DateTimeValuePropertyChangedCallback));
You may ask why this isn't also necessary on the two "internal" bindings in the UserControl's XAML, but both the DatePicker.SelectedDate and the TextBox.Text property are already registered with BindsTwoWayByDefault.
My goal is to create add Text property to RichTextBox. I created Attached Property and set binding to ViewModel's property. Unfortunately changing text in RichTextBox doesn't update underlying property.
Here is my code:
View.cs:
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
}
public static string GetText(DependencyObject obj)
{
return (string)obj.GetValue(TextProperty);
}
public static void SetText(DependencyObject obj, string value)
{
obj.SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached
(
"Text",
typeof(string),
typeof(KnuthMorrisPrattView),
new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = PropertyChangedCallback,
CoerceValueCallback = CoerceValueCallback,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus
}
);
private static object CoerceValueCallback(DependencyObject dependencyObject, object baseValue)
{
throw new NotImplementedException();
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
throw new NotImplementedException();
}
}
View.xaml:
<UserControl x:Class="Launcher.Views.KnuthMorrisPrattView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:views="clr-namespace:Launcher.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
DataContext="{Binding KnuthMorrisPrattViewModel, Source={StaticResource MainViewModel}}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Label Content="Text" DockPanel.Dock="Top"></Label>
<RichTextBox x:Name="TextBox" views:KnuthMorrisPrattView.Text="{Binding TextToSearchArg}"/>
</DockPanel>
<DockPanel Grid.Row="1">
<Label Content="Pattern" DockPanel.Dock="Top"></Label>
<TextBox Text="{Binding PatternArg}"/>
</DockPanel>
</Grid>
ViewModel.cs:
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using GalaSoft.MvvmLight.CommandWpf;
using Launcher.Runners.KnuthMorrisPratt;
namespace Launcher.ViewModels
{
public class KnuthMorrisPrattViewModel : ViewModelBase
{
private string _textToSearchArg;
private string _patternArg;
public string TextToSearchArg
{
get { return _textToSearchArg; }
set
{
_textToSearchArg = value;
RaisePropertyChanged();
}
}
public string PatternArg
{
get { return _patternArg; }
set
{
_patternArg = value;
RaisePropertyChanged();
}
}
public KnuthMorrisPrattViewModel()
{
}
}
}
I know that Callback throws and exception but my goal here is to just see under the debugger that this callback is invoked. Then I add correct implementation.
EDIT:
I think I missed important note about my issue. When I update TextToSearchArg property from code everything works correctly. The only problem is that when I set some text in RichTextBox underlying property is not updated.
What am I missing? Thanks a lot in advance.
Nothing in your code shows that the Attached property is bound to the RichTextBox events, hence it won't ever be called if the content/text in RichTextBox changes.
You'd need to subscribe to the RichTextBox.TextChanged event.
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
this.TextBox.TextChanged += OnTextChanged;
}
...
public void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Get the text from the event and set your Text Property
string text = ...;
SetText(this, text);
}
}
Edit:
In case you want to listen to another control's Dependency/Attached Property changes, use
DependencyPropertyDescriptor.FromProperty(ControlClassName.DesiredPropertyProperty, typeof(ControlClassName)).AddValueChanged(dependencyObject, OnDesiredPropertyChanged);
Where...
ControlClassName is the class containing the Dependency Property (i.e. RichTextBox in your case or the class where the Dependency/Attached Property is defined
'DesiredPropertyPropertyis the name of your property (i.e.TextProperty`
dependencyObject is the instance of object where the DesiredPropertyProperty is set on
OnDesiredPropertyChanged method to call when the property value changes
On a side note:
You should have Code-Behind in the view. There is no requirement that Dependency Properties or Attached Properties have to be defined inside the same class as the are meant for.
Code behind should only be used, if it's an reusable User Control but the naming of your class suggest it's not a User Control (even though it derives form User Control) but a View.
A view is application specific and can't be reused outside that specific app and is only meant to display a certain content. If you make a "LoginControl" then it it can be made to be reusable in other. A "LoginView" on other side doesn't suggest re-usability.
Maybe
Mode=TwoWay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
on the binding missing?
I created a project using MVVM pattern (or so I thought ;) ). To simplify my case:
Model:
public class Model {
public string Name { get; set; }
public bool IsDefective { get; set; }
}
ViewModel - extends MvvmLight ViewModelBase:
public class ViewModel : ViewModelBase {
private ObservableCollection<Model> models;
public ObservableCollection<Model> Models {
get {
if (_models== null) {
_models= new ObservableCollection<Models>();
}
return _models;
}
set {
RaisePropertyChanged("Models");
_models= value;
}
}
}
View - I'm showing a list of textboxes:
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=.IsDefective}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
My scenario is like so: some methods in the Model class may change the IsDefective property, but since my model does not implement the INotifyPropertyChanged interface, my view does not know about such changes. How should this problem be resolved "the mvvm way"? I stumbled upon this question here in SO, but to be honest after reading both highest voted answers and the discussion in comments, I'm more confused: MVVM - PropertyChanged in Model or ViewModel? . I'm willing to agree with Jonathan Allen, because it's just more natural for me to bind this way, but as a beginner in the mvvm pattern I may be missing something. So, am I?
Generally you want your model to be a dumb data transfer object. When you do a database query, you get a dumb model back that doesn't do any transformations because otherwise you're failing to follow Separation of Concerns in SOLID principals. However, cheating a little won't kill you, but it might make debugging something a little frustrating because most people won't expect their POCO (plain old CLR object) to initiate any business logic.
Here's some code:
Some setup classes:
ViewModelBase.cs
A "smarter" version of the ViewModelBase from galasoft, this bad boy autowires up design time view models (you'll like this one)
namespace WPFPlayground.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property != null)
{
if (property.Equals(value)) return;
}
OnPropertyChanged(propertyName);
property = value;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
DefectiveToBackgroundColorConverter.cs
A value converter for our use when our product is being displayed on the view (you'll see it referenced later):
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace WPFPlayground
{
public class DefectiveToBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToBoolean(value))
{
return new SolidColorBrush(Colors.Red);
}
return new SolidColorBrush(Colors.White);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
Using Model-first method:
ProductModel.cs
POCO DTO
namespace WPFPlayground.Model
{
public class ProductModel
{
public string Name { get; set; }
public bool IsDefective { get; set; }
}
}
ProductViewModel.cs
Notice the use of setvalue to automatically wire up the notifypropertychanged event.
namespace WPFPlayground.ViewModel
{
public class ProductViewModel : ViewModelBase
{
private string _name;
private bool _isDefective;
public bool IsDefective
{
get { return _isDefective; }
set { SetValue(ref _isDefective, value); }
}
public string Name
{
get { return _name; }
set { SetValue(ref _name, value); }
}
}
}
So we have a productmodel and a productviewmodel. One does all the work when you're interacting with the database, and one does all the work when you bind to your views.
So we'll need a view that represents just a single productviewmodel:
ProductView.xaml
Notice the use of the background color converter to handle our triggers
<UserControl x:Class="WPFPlayground.View.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:wpfPlayground="clr-namespace:WPFPlayground"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance wpfPlayground:DesignProductViewModel, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<wpfPlayground:DefectiveToBackgroundColorConverter x:Key="DefectiveToBackgroundColorConverter" />
</UserControl.Resources>
<Viewbox>
<Border Width="500" Background="{Binding IsDefective, Converter={StaticResource DefectiveToBackgroundColorConverter}}">
<TextBlock Text="{Binding Name}" FontSize="40" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</Viewbox>
</UserControl>
Next we'll need that design time viewmodel so we can view our XAML in design time:
DesignProductViewModel.cs
A bit boring, but it makes design time work!
using WPFPlayground.ViewModel;
namespace WPFPlayground
{
public class DesignProductViewModel : ProductViewModel
{
public DesignProductViewModel()
{
Name = "This is my product";
IsDefective = true;
}
}
}
Now we need to display a list of these viewmodels:
MainWindow.xaml
Itemscontrol all day err day
<Window x:Class="WPFPlayground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="clr-namespace:WPFPlayground.ViewModel"
xmlns:view="clr-namespace:WPFPlayground.View"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance viewModel:DesignProductsViewModel, IsDesignTimeCreatable=True}">
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:ProductViewModel}">
<view:ProductView />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding Products}">
<view:ProductView />
</ItemsControl>
</StackPanel>
</Window>
DesignProductsViewModel.cs
The design time view model so you can see this working in design time. It generates an easy random set of products.
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace WPFPlayground.ViewModel
{
public class DesignProductsViewModel : ProductsViewModel
{
public DesignProductsViewModel()
{
var random = new Random();
Products = new ObservableCollection<ProductViewModel>(Enumerable.Range(1, 5).Select(i => new ProductViewModel
{
Name = String.Format(#"Product {0}", i),
IsDefective = (random.Next(1, 100) < 50)
}));
}
}
}
Your not missing any thing , Mvvm and it's counter parts are suggestions which help you create maintainable , testable and decoupled pieces of code.
When you come across a situation where you duplicate code just to satisfy Mvvm you can "cheat".
It is perfectly legitimate for your model to implement INotifyPropertyChanged.
It's very popular in 'CRUD' applications.
I have read through a few articles on this and I can't see what im doing wrong here could anyone help :)
I have a UserControl called CreateRuleItemView I want to add a Dependency Property on here that I can bind my ViewModel too. So far I have.
public partial class CreateRuleItemView : UserControl
{
public CreateRuleItemView()
{
InitializeComponent();
}
public Boolean ShowEditTablePopup
{
get
{
return (Boolean)this.GetValue(ShowEditTablePopupProperty);
}
set
{
this.SetValue(ShowEditTablePopupProperty, value);
}
}
public static readonly DependencyProperty ShowEditTablePopupProperty = DependencyProperty.Register("ShowEditTablePopup", typeof(Boolean), typeof(CreateRuleItemView), new PropertyMetadata(null, OnShowEditTablePopupChanged));
private static void OnShowEditTablePopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
}
If I then try to access the property in the User Control Xaml I get:
<UserControl x:Class="Views.Setup.CreateRuleItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance Type=vm:CreateRuleItemViewModel, IsDesignTimeCreatable=False}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" ShowEditTablePopup="{Binding DataContext.ShowEditTablePopup}" >
Error 1 The member "ShowEditTablePopup" is not recognized or is not accessible.
Error 3 The property 'ShowEditTablePopup' does not exist on the type 'UserControl'
Error 2 The property 'ShowEditTablePopup' was not found in type 'UserControl'.
Edit 1:
Ok Managed to get around this by adding the binding in the code behind on my Main window where i setup my view.
Setup.CreateRuleItemView v = new Setup.CreateRuleItemView();
BindingOperations.SetBinding(v, CreateRuleItemView.EditTablePopupProperty, new Binding("EditTablePopup"));
You won't be able to achieve this with a UserControl (I've just tried replacing the <UserControl... partial declaration in XAML with <local:CreateRuleItemView when recreating the code locally, but this results in a circular reference and thus won't compile/will potentially result in a XamlParseException). I'd write a control inheriting from ContentControl to which you can add the property and template it instead (I did this with WPF so the namespaces may differ, otherwise the code will work):
using System;
using System.Windows;
using System.Windows.Controls;
namespace DepPropTest
{
/// <summary>
/// Description of CreateRuleItemView.
/// </summary>
public class CreateRuleItemView : ContentControl
{
public CreateRuleItemView()
{
}
public static readonly DependencyProperty ShowEditTablePopupProperty =
DependencyProperty.Register("ShowEditTablePopup", typeof (bool), typeof (CreateRuleItemView), new PropertyMetadata());
public bool ShowEditTablePopup
{
get { return (bool) GetValue(ShowEditTablePopupProperty); }
set { SetValue(ShowEditTablePopupProperty, value); }
}
}
}
Then you can use it as follows (this example uses WPF by the way, hence Window being the parent control):
<Window x:Class="DepPropTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DepPropTest"
Title="DepPropTest" Height="300" Width="300">
<local:CreateRuleItemView Width="300"
Height="300"
ShowEditTablePopup="True">
<local:CreateRuleItemView.Template>
<ControlTemplate>
<!-- define your control's visual appearance... -->
</ControlTemplate>
</local:CreateRuleItemView.Template>
<TextBox Text="Some content for your view" />
</local:CreateRuleItemView>
</Window>
I have a UserControl in my Silverlight application and for some reason, the DependencyProperty of a UserControl is not set if I bind it to a value in the view model. I spent several hours of debugging in a trial-and-error fashion now and I'm all out of ideas what to try next.
I can reproduce the issue in a new Silverlight project with
MainPage.xaml
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<local:MyCtrl HeaderText="{Binding HeaderText}"/>
<TextBlock Text="{Binding HeaderText}"/>
</StackPanel>
</Grid>
</UserControl>
MainPage.xaml.cs
using System.Windows.Controls;
namespace SilverlightApplication1
{
public class Vm
{
public string HeaderText
{ get; set; }
}
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new Vm() { HeaderText = "My Header" };
}
}
}
MyCtrl.xaml (added as new "Silverlight User Control")
<UserControl x:Class="SilverlightApplication1.MyCtrl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txtHeader" />
</Grid>
</UserControl>
MyCtrl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class MyCtrl : UserControl
{
public MyCtrl()
{
InitializeComponent();
}
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText", typeof(string), typeof(MyCtrl), null);
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set
{
// NEVER CALLED
SetValue(HeaderTextProperty, value);
this.txtHeader.Text = value;
}
}
}
}
The rest of the project is used "as is", i.e. no compiler options were changed and the server part is also left "as is".
Observations:
I see that the getter of Vm.HeaderText is called during a binding
operation but the setter of the DependencyProperty MyCtrl.HeaderText is never called.
The TextBlock in MainPage below the custom control displays the bound value correctly.
There are no compiler warnings.
There are no exceptions thrown.
There are no debug outputs while the application runs.
This feels like something important is silently failing where it shouldn't.
Maybe I can shed some light on your observations and assumptions:
You can update child controls inside a DependencyProperty setter. Call your setter and see for yourself, the update will be performed. You falsely assume the binding engine is obliged to call your setter. Well, it just calls SetValue(HeaderTextProperty, newValue); on your control, no need to call the setter.
Your observation is specified behaviour.
As you figured out, the right way to do it is to use a propertyChanged callback.
The property getter on your viewmodel Vm.HeaderText is called by the binding engine, because your viewmodel is no DependencyObject and HeaderText is no DependencyProperty so there is no SetValue(...) and no GetValue(...)
You can't see any compiler warnings or exceptions, nor is anything silently failing, because there is nothing wrong with your scenario.
Well, seems like you cannot update the child controls inside a DependencyProperty setter (would be interesting to know why and if this is specified behaviour...)
Using this instead works:
public static readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText", typeof(string), typeof(MyCtrl), new PropertyMetaData(OnHeaderTextChanged));
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
private static void OnHeaderTextChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var ctrl = d as MyCtrl;
ctrl.txtHeader.Text = (string)e.NewValue;
}