Validating dependency property with INotifyDataErrorInfo - c#

I have a usercontrol with a single Textbox inside a grid like this:
<TextBox Text="{Binding Username}"></TextBox>
The code-behind of the usercontrol implements INotifyDataErrorInfo and INotifyPropertyChanged. This is how my code-behind looks like (Apart from the above mentioned interface implementations)
public TestControl()
{
InitializeComponent();
this.DataContext = this;
}
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
if (_username.Length < 3)
SetErrors("Username", new List<string> { "Usernames should be at least 3 characters long" });
OnPropertyChanged("Username");
}
}
Where SetErrors is just a function which adds an error to the IEnumerable which the INotifyDataErrorInfo.GetErrors will return. This works pretty well. When I write text less than 3 characters, the textbox turns red. That is exactly what I expect.
Now I want the MainWindow's viewmodel to set this textbox. To do that, the Username field should be a dependency property so I can bind to it. But the problem is that I can't validate it now. I made a dependency property and tried validating it at the ValidateValueCallback but INotifyDataErrorInfo members are not static. So I can't reach them. What should I do?

Place Username within MainViewModel and inside of UserControl bind to it using RelativeSource binding like
"{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.Username}"
You can replace explicit name indication with CallerMemberName attribute.
EDIT
When you define dependency property you can determine event which will be raised whenever value's change occurs. As a parameter it has reference to class, in which it is defined, as far as your project is concerned it would be your UserControl. You can then invoke any method on this object, no need to be static at all. Following code depicts the concept, not precise solution, adjust it to your requirement:
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty.Register(
"PropertyType", typeof (propertyType), typeof (PasswordBoxDP), new PropertyMetadata((x, y) =>
{
var userControlClass = x as UserControlClass;
userControlClass.Validate();
}));
private void Validate()
{
}
By the way, binding in your case will not be working. You defined DataContext refering to itself so when you set a binding on your dependency property it will kick off seeking within UserControl.

Related

Dotvvm run-time binding to 'object' type

i have this class
public class Property{
public string Name {get;set;}
public object Value {get;set;}
}
i want to create list of the above class List<Property> and dynamically add Mark Up Controls Code only
, so as their website they have an example HERE and what i did to that example is adding a public property of type Property to the TextBoxWithLabel class and changed the setter of the above example for binding as follows:
[MarkupOptions(AllowHardCodedValue = false)]
public string Text
{
get { return (string)GetValue(TextProperty); }
set {
SetValue(TextProperty, value);
Property.Value = value;
}
}
public static readonly DotvvmProperty TextProperty
= DotvvmProperty.Register<string, TextBoxWithLabel>(c => c.Text, "");
when i run the app and type something in the input field, the Value property of Type Property still null and here is where i'm stuck
i tried also to debug setter and it turns out it does not reach there so there is problem with run-time binding, which is 'as their example' this line of code
textBox.SetBinding(TextBox.TextProperty, GetValueBinding(TextProperty));
any help will appreciated :)
EDIT:
for more clarification,i have page called MainAppPage
and Markup Control with code behind called ContentControl
simply , MainAppPage passes List<Property> to ContentControl using this in MainAppPage
<controls:ContentControl Instance="{value: ClassObject}"/> then ContentControl start iterating through List<Property> and creating InputField's that derive from HtmlGenericControl
InputField's rendering like a charm in ContentControl
only thing is not working is binding , so again, how to bind Property.Value to InputField.Text so any changes happens in UI from user reflects on Property.Value after the InputField gets unfocused like any other MVVM pattern ?
DotVVM does not assign to the property usning the setter, is sets the underlying property store in DotvvmBindableObject instead. It's very simmilar what WPF does with their DependencyProperty, it's needed to represent the data bindings. You can actually completely omit the C# property declaration, declaring the field TextProperty and calling the DotvvmProperty.Register is enough to declare a property for dotvvm.
Other "problem" is that the controls do not store any data, everything has to be persisted in the view model. You can only use the control properties to data-bind a view model property. I think we are running here into a XY problem, I can only tell why your code does not work, but I have no idea what are actually trying to do...
Anyway, if you just want to "bind" your control to a view model property, have a look at https://www.dotvvm.com/docs/tutorials/control-development-markup-controls-with-code/2.0. You can declare the property like that:
[MarkupOptions(AllowHardCodedValue = false)]
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DotvvmProperty TextProperty
= DotvvmProperty.Register<string, TextBoxWithLabel>(c => c.Text, "");
Use it in the markup of you control
#baseType FullName.Of.YourControl
{{value: _control.Text}}
And use the control in your page (or other control)
<cc:YourControl Text="{value: _this.Property.Value}" />

WPF Cant find source for binding with reference - xaml

I'm new to WPF and xaml and I'm making a video player. I'm currently trying to bind the movie time slider to the current elapsed time which I store in a TimeSpan variable which is updated every second thru a DispatcherTimer.
This is my xaml:
<customControls:ThumbDragSlider x:Name="sMovieSkipSlider" Height="25" Margin="65,0,65,71" VerticalAlignment="Bottom"
Value="{Binding ElementName=_movieElapsedTime, Path = TotalSeconds, Mode=OneWay}"
DragStarted="SMovieSkipSlider_OnDragStarted"
DragCompleted="SMovieSkipSlider_OnDragCompleted"/>
This is how the variable is declared:
private TimeSpan _movieElapsedTime;
And this is the error I'm getting:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=_movieElapsedTime'. BindingExpression:Path=TotalSeconds; DataItem=null; target element is 'ThumbDragSlider' (Name='sMovieSkipSlider'); target property is 'Value' (type 'Double')
ElementName is used to refer to an element in the XAML. If you had a control such as...
<TextBox x:Name="_movieElapsedTime" />
Then it would make sense the way you have it -- if it happened to have a property named TotalSeconds.
You also can't bind to a field; it has to be either a regular C# property with a get and maybe a set, or else a special kind of property called a dependency property.
But let's do this with a viewmodel. A viewmodel is any random class that implements INotifyPropertyChanged, and raises the PropertyChanged event when its property values change. It keeps track of the internals of your application. It isn't aware that a user interface exists. It exposes data properties like MovieElapsedTime, and may expose commands as well which allow buttons or menu items to send orders to the viewmodel. It may also have methods that its parent viewmodel may call.
We'll write a viewmodel base class that implements INotifyPropertyChanged, and derive a simple viewmodel from it that represents the things that a video player needs to know. Then we'll create a UI in XAML that lets the user interact with it.
You'll probably want the viewmodel to have commands to start and stop the video and so on. That's easy to find on Google. I'd recommend using a RelayCommand/DelegateCommand class; google those and you'll see what they do. There are a lot of examples out there you can steal the code for.
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region VideopPlayerViewModel Class
public class VideopPlayerViewModel : ViewModelBase
{
#region MovieElapsedTime Property
private TimeSpan _movieElapsedTime = default(TimeSpan);
public TimeSpan MovieElapsedTime
{
get { return _movieElapsedTime; }
set
{
if (value != _movieElapsedTime)
{
_movieElapsedTime = value;
OnPropertyChanged();
}
}
}
#endregion MovieElapsedTime Property
}
#endregion VideopPlayerViewModel Class
MainWindow constructor:
public MainWindow()
{
InitializeComponent();
DataContext = new VideoPlayerViewModel();
}
XAML. Because A VideoPlayerViewModel is the DataContext for our window, that means that when you tell a binding to bind to MovieElapsedTime, with no further information about where to find it, it will go to the DataContext object it inherited from the window.
<customControls:ThumbDragSlider
Value="{Binding MovieElapsedTime.TotalSeconds, Mode=OneWay}"
x:Name="sMovieSkipSlider"
Height="25"
Margin="65,0,65,71"
VerticalAlignment="Bottom"
DragStarted="SMovieSkipSlider_OnDragStarted"
DragCompleted="SMovieSkipSlider_OnDragCompleted"/>
Non-MVVM version
Here's the dependency property version. It's not "the right way to do it" but it's not totally awful.
Next question: What is MovieElapsedTime a member of? The Window? What is the DataContext? If you set DataContext = this and implemented INotifyPropertyChanged on your window, and raise PropertyChanged when MovieElapsedTime changes, that's a bad idea for other reasons, but your binding will work with MovieElapsedTime as a conventional property. If not, you need this:
<customControls:ThumbDragSlider
Value="{Binding MovieElapsedTime.TotalSeconds, Mode=OneWay, RelativeSource={RelativeSource AncestorType=Window}}"
x:Name="sMovieSkipSlider"
Height="25"
Margin="65,0,65,71"
VerticalAlignment="Bottom"
DragStarted="SMovieSkipSlider_OnDragStarted"
DragCompleted="SMovieSkipSlider_OnDragCompleted"/>
Window codebehind:
public TimeSpan MovieElapsedTime
{
get { return (TimeSpan)GetValue(MovieElapsedTimeProperty); }
set { SetValue(MovieElapsedTimeProperty, value); }
}
public static readonly DependencyProperty MovieElapsedTimeProperty =
DependencyProperty.Register(nameof(MovieElapsedTime), typeof(TimeSpan), typeof(MainWindow),
new PropertyMetadata(null));
Define the property that way instead of what you have. With all that stuff, the control will receive notifications automatically when you set the property to a new value.
You should really write a viewmodel which implements INotifyPropertyChanged and make this a property of the viewmodel. We can go through that if you're interested.
I think you'll need to poll more than once a second, though, if you want the update to be smooth. More likely every 500 ms or even 250. Try 500 and see how it looks.

How to pass parameters to usercontrol's viewmodel through window's xaml?

How to pass parameters to usercontrol's viewmodel through window's xaml? I am using MVVM pattern.
I have tried creating a dependency property as below. But passing it in constructoe of viewmodel throws "nonstatic properties cannot be field initializers" exception.
xaml.cs of usercontrol
public partial class SomeView : UserControl
{
SomeViewModel vm = new SomeViewModel(ForeColor);
public SomeView()
{
InitializeComponent();
this.DataContext = vm;
}
public Color ForeColor
{
get { return (Color)this.GetValue(ForeColorProperty); }
set { this.SetValue(ForeColorProperty, value); }
}
public static readonly DependencyProperty ForeColorProperty = DependencyProperty.Register("ForeColor", typeof(Color), typeof(SomeView ));
}
and this is how I am calling the user control
<local:SomeView ForeColor="{Binding Foreground}"/>
Foreground is a property of type System.Drawing.Color
As the error stated you cannot refer instance fields from field initializers.
Move the initialization logic to constructor instead:
SomeViewModel vm;
public SomeView()
{
InitializeComponent();
vm = new SomeViewModel(ForeColor);
this.DataContext = vm;
}
I would suggest you to always keep the View's code behind as simple as possible, making this way a cleaner app and keeping in mind the MVVM pattern concept.
Based on my understanding, you would want to update the property which has changed on the View to its ViewModel. Otherwise, you could just define the property in the ViewModel without passing it from the View.
Therefore, defining the Property on the ViewModel anyway, you could update its value by specifying the Binding Mode as "OneWayToSource" or "TwoWay". No Property instantiation would be needed on the View's code behind for passing through parameter. The Binding with its DataContext would do the work. In addition, to get these binding modes to work, you may set the UpdateSourceTrigger property on the Binding block.
You can find more information on the following MSDN site:
Binding.Mode Property
Binding.UpdateSourceTrigger Property
I hope this helped you, Regards.

Custom WPF binding not being updated on listbox selection

I'm puzzled by this probably trivial matter. I have my custom property on a descendant of DevExpresses class LayoutGroup (shouldn't make any difference IMO):
public class ExpandableLayoutGroup : LayoutGroup
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(Boolean), typeof(ExpandableLayoutGroup));
public Boolean IsExpanded
{
get { return (Boolean) GetValue(IsExpandedProperty); }
set
{
expandCheckBox.IsChecked = value;
SetValue(IsExpandedProperty, value);
}
}
}
Then I have XAML binding to a listbox (containing 2 items at this time) called listStates.
<ExpandableLayoutGroup x:Name="groupTool" IsExpanded="{Binding SelectedValue.IsTool, ElementName=listStates}" DataContext="{Binding Tool}" Header="Tool" View="GroupBox" />
The object list binding to listStates contains both properties (simplified):
public class State
{
public Boolean IsItemTool { get; private set; }
public Tool Tool { get; private set; }
}
Am I missing something obvious? Because I would expect that when I change listbox selection (single) it would also update IsExpanded property on a group. I have more subproperties of Tool binded (as apparent by DataContext="{Binding Tool}") and those update well. So the DataContext is changing correctly on listbox selection. Except this one property IsExpanded (that should expand/collapse layoutgroup).
What am I doing wrong, and how to make it so, that when listbox changes, IsExpanded binding is polled to get value from IsTool, and will set it (depending on selection).
Getters and setters of dependency properties must contain only GetValue and SetValue calls, because there're two ways to get and set their values: using getters and setters and using DependencyProperty or DependencyPropertyKey. This is why code in your setter is not executed (bindings don't use them for dependency properties). If you want some code to be executed when the value is changed, specify it as a PropertyChangedCallback callback in PropertyMetadata.
See PropertyChangedCallback Delegate on MSDN.
The setter for a dependency property must only set the value for it's underlying type. The .NET internals reserve the right to call SetValue() directly (and in my experience WPF usually does, while Silverlight uses the setter that you've defined).
Refer to the "Implications for Custom Dependency Properties" section of XAML Loading and Dependency Properties for details

How to achieve databinding with a user control in WPF?

I'm fairly new to WPF and I have some problems getting databinding to work as I want. I've written a user control which contains a TextBox whose Text-Property I want to bind to a property of my UserControl, which I want to bind again to something else.
What am I missing?
XAML
<!-- User Control -->
<TextBox Text="{Binding Path=TheText}" />
<!-- Window -->
<WpfApplication1:SomeControl TheText="{Binding Path=MyStringProp}" />
C#
// User Control ----
public partial class SomeControl : UserControl
{
public DependencyProperty TheTextProperty = DependencyProperty
.Register("TheText", typeof (string), typeof (SomeControl));
public string TheText
{
get
{
return (string)GetValue(TheTextProperty);
}
set
{
SetValue(TheTextProperty, value);
}
}
public SomeControl()
{
InitializeComponent();
DataContext = this;
}
}
// Window ----
public partial class Window1 : Window
{
private readonly MyClass _myClass;
public Window1()
{
InitializeComponent();
_myClass = new MyClass();
_myClass.MyStringProp = "Hallo Welt";
DataContext = _myClass;
}
}
public class MyClass// : DependencyObject
{
// public static DependencyProperty MyStringPropProperty = DependencyProperty
// .Register("MyStringProp", typeof (string), typeof (MyClass));
public string MyStringProp { get; set; }
// {
// get { return (string)GetValue(MyStringPropProperty); }
// set { SetValue(MyStringPropProperty, value); }
// }
}
Best RegardsOliver Hanappi
PS: I've tried to implement the INotifyPropertyChanged interface on my user control, but it did not help.
You want to bind the Text property of your TextBox back to the TheText property of the UserControl it lives in, right? So you need to tell the binding where the property lives. There's a couple of ways to do this (you can do it with a RelativeSource using FindAncestor) but the easiest way is to give the UserControl a "name" in the XAML and bind using element binding:
<UserControl ...
x:Name="me" />
<TextBox Text="{Binding TheText,ElementName=me}" />
</UserControl>
Now your TextBox will reflect the value you've assigned (or bound) to your "SomeControl.TheText" property - you needn't change any of your other code, although you'll probably want to implement INotifyPropertyChanged on your underlying MyClass object so that the binding knows when the property has changed.
Matt has provided a solution to your problem. Here is a little more explanation and a hint to stop this problem in future.
As SomeControl.DataContext is set in the SomeControl constructor, the window's binding TheText="{Binding Path=MyStringProp}" has a Source of type SomeControl, not MyClass as you intended.
Any bindings that fail at runtime cause debug messages to be logged to the output panel of Visual Studio. In this case, you would have seen that no such property 'MyStringProp' exists on object of type 'SomeControl', which should have raised your suspicions.
I think everyone finds WPF data binding takes some time to learn and especially to debug, but persevere. Data binding in WPF is really fantastic, and I still get a kick out of knowing how easily it makes the data on my UIs stay up to date.

Categories

Resources