The question is: How to get/set the VisualState of a Control (with more than two Visual States) on the View through my ViewModel in MVVM pattern (with zero-view-code-behind)?
I've seen similar questions who's answers didn't work for me:
Binding [VisualStateManager] view state to a MVVM viewmodel?
How to change VisualState via ViewModel
Note: below I'll be explaining what was wrong with the answers in the mentioned questions. If you know a better approach, you can dismiss reading the rest of this question.
As for the first question, the accepted answer's approach doesn't work for me. Once I type the mentioned XAML code
<Window .. xmlns:local="clr-namespace:mynamespace" ..>
<TextBox Text="{Binding Path=Name, Mode=TwoWay}"
local:StateHelper.State="{Binding Path=State, Mode=TwoWay}" />
</Window>
It shows a design-time error that says: The attachable property 'State' was not found in type 'StateHelper'., I tried to get over this by renaming StateHelper.StateProperty to StateHelper.State, ending up with two errors..
1: The attachable property 'State' was not found in type 'StateHelper'. and
2: The local property "State" can only be applied to types that are derived from "StateHelper".
As for the second question, the accepted answer's approach doesn't work for me. After fixing VisualStateSettingBehavior's syntax errors to be:
public class VisualStateSettingBehavior : Behavior<Control>
{
private string sts;
public string StateToSet
{
get { return sts; }
set
{
sts = value;
LoadState();
}
}
void LoadState()
{
VisualStateManager.GoToState(AssociatedObject, sts, false);
}
}
I got a design-time error on the line
<local:VisualStateSettingBehavior StateToSet="{Binding State}"/>
that says: A 'Binding' cannot be set on the 'StateToSet' property of type 'VisualStateSettingBehavior'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
I tried to merge the two solutions by making VisualStateSettingBehavior.StateToSet a dependency property, but I got other design-time errors in the View.
Any suggestions?
At last, I could solve this. The solution was similar to the first question's best answer. I found out that in my case there are some constraints on the View.xaml to use an attached property:
It has to be registered via DependencyProperty.RegisterAttached.
It has to be static.
It must has a property instance (getter/setter).
I got through that with this coding-style in mind, and the final approach was:
VisualStateApplier:
public class VisualStateApplier
{
public static string GetVisualState(DependencyObject target)
{
return target.GetValue(VisualStateProperty) as string;
}
public static void SetVisualState(DependencyObject target, string value)
{
target.SetValue(VisualStateProperty, value);
}
public static readonly DependencyProperty VisualStateProperty =
DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(VisualStateApplier), new PropertyMetadata(VisualStatePropertyChangedCallback));
private static void VisualStatePropertyChangedCallback(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
VisualStateManager.GoToElementState((FrameworkElement)target, args.NewValue as string, true); // <- for UIElements, OR:
//VisualStateManager.GoToState((FrameworkElement)target, args.NewValue as string, true); // <- for Controls
}
}
View:
<!--A property inside the object that owns the states.-->
<local:VisualStateApplier.VisualState>
<Binding Path="State"/>
</local:VisualStateApplier.VisualState>
ViewModel:
private string _state;
public string State
{
get { return _state; }
set
{
_state = value;
RaisePropertyChanged("State");
}
}
Related
If I have a WPF view with a textbox that has a binding to a decimal (or any other number format) I automatically get a visual hint if I enter a letter or any other invald character and the value is not transferred to the viewmodel (the breakpoint on the setter is never reached). If I enter a number, everything works fine. To disable my save-Button (ICommand), I would like to get the info in my viewmodel that there is an error in the view in a MVVM-like fashion. Hints to where this behavior is documented are very welcome!
So the target situation looks like this:
what I want would be a disable "save and close":
XAML:
<TextBox Text="{Binding Path=SelectedItem.Punkte_Seite_max, UpdateSourceTrigger=PropertyChanged}"/>
ViewModel
public int Punkte_Seite_max
{
get { return _punkte_Seite_max; }
set
{
_punkte_Seite_max = value;
Changed(); //INotifyPropertyChanged call
}
}
What you want to be using is INotifyDataErrorInfo documentation found here. This lets you provide custom validation on the properties that you have bound to your ViewModel.
This is a sample I have shamelessly copied from CodeProject but I have done so to prevent any link rot. I have also tried to adapt it slightly to match your example.
ViewModel
public class ViewModel : INotifyDataErrorInfo
{
// A place to store all error messages for all properties.
private IDictionary<string, List<string>> propertyErrors = new Dictionary<string, List<string>>();
public string Preis
{
get { return _preis; }
set
{
// Only update if the value has actually changed.
if (!string.Equals(_preis, value, StringComparison.Ordinal))
{
_preis = value;
Changed();
this.Validate();
}
}
}
// The event to raise when the error state changes.
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
// A method of getting all errors for the given known property.
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (propertyName != null)
{
if (propertyErrors.TryGetValue(propertyName, out var errors))
{
return errors;
}
}
return null;
}
// Whether there are any errors on the ViewModel
public bool HasErrors
{
get
{
return propertyErrors.Values.Any(r =>r.Any());
}
}
private void Validate()
{
// 1. HERE YOU CAN CHECK WHETHER Preis IS VALID AND ANY OTHER PROPERTIES
// 2. Update the 'propertyErrors' dictionary with the errors
// 3. Raise the ErrorsChanged event.
}
}
XAML
You will need to change your XAML to something like this:
<TextBox>
<Binding Path="Preis" UpdateSourceTrigger="PropertyChanged" ValidatesOnNotifyDataErrors="True"/>
</TextBox>
Thanks to Bijington I got on the right track and found an answer which satisfies MVVM and also doesn't need code behind. In case someone is interested here's my solution to this issue.
The error shown above is created in the view because there is no converter in WPF from letters to int (how should there be one). To raise this issue the binding in needs to have NotifyOnValidationError=True.
<TextBox Text="{Binding Path=SelectedItem.Punkte_Seite_max, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}"
This raises a bubbling up Validation.Error event that can be captured anywhere in the tree. I decided to capture it via a routed event trigger like so:
XAML:
<Window
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
<i:Interaction.Triggers>
<userInterface:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}" >
<userInterface:ViewErrorCounterAction ViewErrorCounter="{Binding Path=ViewValidationErrorCount, Mode=TwoWay}"/>
</userInterface:RoutedEventTrigger>
</i:Interaction.Triggers>
So the twoway-binding is the MVVM-okayish link to my viewmodel.
ViewErrorCounterAction is based on this SO answer:
public class ViewErrorCounterAction : TriggerAction<DependencyObject> {
public ViewErrorCounterAction()
{
ViewErrorCounter = 0; // initalize with 0 as there should not be such errors when the window is loaded
}
public int ViewErrorCounter
{
get
{
return System.Convert.ToInt32(GetValue(ViewErrorCounterProperty));
}
set
{
SetValue(ViewErrorCounterProperty, value);
}
}
public static readonly DependencyProperty ViewErrorCounterProperty = DependencyProperty.Register("ViewErrorCounter", typeof(int), typeof(ViewErrorCounterAction), new PropertyMetadata(null));
protected override void Invoke(object parameter)
{
var e = (ValidationErrorEventArgs)parameter;
if ((e.Action == ValidationErrorEventAction.Added))
ViewErrorCounter = ViewErrorCounter + 1;
else if ((e.Action == ValidationErrorEventAction.Removed))
ViewErrorCounter = ViewErrorCounter - 1;
}
}
Finally routed Event Trigger is based on https://sergecalderara.wordpress.com/2012/08/23/how-to-attached-an-mvvm-eventtocommand-to-an-attached-event/
Hope this helps and I'd appreciate comments on how to better solve this issue if there are more elegant ways :)
UWP came with a new way of DataBinding, Compiled Binding, using the {x:Bind} markup extension, when I was discovering this new feature, I found out that we can actually bind an event to a method !
Example :
Xaml :
<Grid>
<Button Click="{x:Bind Run}" Content="{x:Bind ButtonText}"></Button>
</Grid>
Code Behind :
private string _buttonText;
public string ButtonText
{
get { return _buttonText; }
set
{
_buttonText = value;
OnPropertyChanged();
}
}
public MainPage()
{
this.InitializeComponent();
ButtonText = "Click !";
}
public async void Run()
{
await new MessageDialog("Yeah the binding worked !!").ShowAsync();
}
The result :
And since {x:Bind} bindings are evaluated at runtime and the compiler generates some files that represent that binding, so I went there to investigate what's going on, so in the MainPage.g.cs file (MainPage is the xaml file in question) I found this :
// IComponentConnector
public void Connect(int connectionId, global::System.Object target)
{
switch(connectionId)
{
case 2:
this.obj2 = (global::Windows.UI.Xaml.Controls.Button)target;
((global::Windows.UI.Xaml.Controls.Button)target).Click += (global::System.Object param0, global::Windows.UI.Xaml.RoutedEventArgs param1) =>
{
this.dataRoot.Run();
};
break;
default:
break;
}
}
The compiler seems to know that it's a valid binding, moreover it creates the corresponding event handler, and it calls the concerned method inside.
That is great ! but why ?? A binding target should be a dependency property, not an event. The official documentation for {x:Bind} does mention this new feature, but doesn't explain why and how can a non dependency property be a target of binding, anyone who has a deep explanation for this ?
Well, it's sort of the new feature of the "x:Bind" compiled binding.
https://msdn.microsoft.com/en-us/library/windows/apps/mt204783.aspx
Event binding is a new feature for compiled binding. It enables you to specify the handler for an event using a binding, rather than it having to be a method on the code behind. For example: Click="{x:Bind rootFrame.GoForward}".
For events, the target method must not be overloaded and must also:
Match the signature of the event.
OR have no parameters.
OR have the same number of parameters of types that are assignable from the types of the event parameters.
I guess your scenario perfectly match item 2.
A binding target should be a dependency property
While this is true for a regular binding, it does not hold for the {x:Bind} markup extension.
So you can in fact have a non-dependency property like e.g.
public sealed partial class MyUserControl : UserControl
{
public Color BackgroundColor
{
get { return ((SolidColorBrush)Background).Color; }
set { Background = new SolidColorBrush(value); }
}
}
as an {x:Bind} target like this:
<local:MyUserControl BackgroundColor="{x:Bind ViewModel.BgColor}" />
while
<local:MyUserControl BackgroundColor="{Binding ViewModel.BgColor}" />
would fail.
Can someone tell me if this is possible. I have a WPF datagrid and I'd like to bind the column headers of the datagrid to properties/fields in the code behind.
So heres what I've tried. This is my column code.
<DataGridTextColumn Header="{Binding ElementName=frmAssetPPM, Path=HeaderProperty}"
And this is what I've added to the window xaml.
<Window .... Name="frmAssetPPM">
And this is my property definition in the code behind:
private const string HeaderPropertyConstant = "Property";
private string _headerProperty = HeaderPropertyConstant;
public string HeaderProperty
{
get { return _headerProperty; }
set { _headerProperty = value; }
}
However, when I run the application, I'm getting this error message displayed in the Output window in VS.
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderProperty; DataItem=null; target element is 'DataGridTextColumn' (HashCode=47624635); target property is 'Header' (type 'Object')
Can someone tell me what I'm doing wrong? Or if I can even do this? I read somewhere that columns are a separate object and this sometimes leads to complications.
Some things are tough to bind because they are not part of the Visual tree, such as popups or in your case - datagrid headers. A well known workaround is to use Josh Smith's DataContextSpy. Basically you instantiate it as a resource and give it the binding. Then use that instance elsewhere, where you can tap into it's data context. There are plenty examples on the web, but to get you started, something like this should work..
<DataGrid.Resources>
<DataContextSpy x:Key="dcSpy"
DataContext="{Binding ElementName=frmAssetPPM, Path=HeaderProperty}"/>
....
then your binding will work:
<DataGridTextColumn Header="{Binding Source={StaticResource dcSpy}, Path=DataContext}"
here's Josh Smith's code if you don't find it on the web:
public class DataContextSpy : Freezable
{
public DataContextSpy ()
{
// This binding allows the spy to inherit a DataContext.
BindingOperations.SetBinding (this, DataContextProperty, new Binding ());
}
public object DataContext
{
get { return GetValue (DataContextProperty); }
set { SetValue (DataContextProperty, value); }
}
// Borrow the DataContext dependency property from FrameworkElement.
public static readonly DependencyProperty DataContextProperty = FrameworkElement
.DataContextProperty.AddOwner (typeof (DataContextSpy));
protected override Freezable CreateInstanceCore ()
{
// We are required to override this abstract method.
throw new NotImplementedException ();
}
}
I have XAML code:
<TextBox Name="textBoxMask1"/>
<TextBox Name="textBoxMask2"/>
<TextBox Name="textBoxMask3"/>
...
<TextBox Name="textBoxMask9"/>
and class in C#:
private static string mask1;
public static string Mask1
{
get { return mask1; }
set { mask1 = value; }
}
private static string mask2;
public static string Mask2
{
get { return mask2; }
set { mask2 = value; }
}
private static string mask3;
public static string Mask3
{
get { return mask3; }
set { mask3 = value; }
}
....
private static string mask9;
public static string Mask9
{
get { return mask9; }
set { mask9 = value; }
}
And I want to bind these TextBoxes with Properties -> textBoxMask1 with Mask1 etc.
Earlier I did this by TextChanged, but I want to make Binding. TooWay Binding, because I want to predefine Mask1, Mask2, Mask3, ..., Mask9 in another C# class, and maybe later change these values - also in some C# code - and I want my changes, to be visible in layout (XAML) and in C# code - so ex. changing Property Mask1 from C# will change Text in TextBox textBoxMask1, and changing Text in textBoxMask1 will change Property Mask1.
I don't understand, how to make connection (binding) between objects XAML and C#.
For a normal Binding you don't need your properties to be static, just public. Here an example:
C# code (for one property)
private string mask1;
public string Mask1
{
get { return mask1; }
set
{
mask2 = value;
RaisePropertyChanged("Mask1");
}
}
It's really important for the binding that the class containing the properties implements the INotifyPropertyChanged interface, and that you raise the corresponding event in the setter of each property. Another option is to make all properties DependecyProperty, but it is usually overkill.
As for the XAML:
<TextBox Name="textBoxMask1" Text="{Binding Mask1, Mode=TwoWay}"/>
(TwoWay Binding is the default for the Text property, but it does not hurt to put it explicitly).
Just make sure that the DataContext of the object containing your TexBoxes (usually an UserControl) is set to a valid instance of your C# class.
By the way, this is a very basic question, that's why you got a negative vote and no answers before mine. What is expected is that you ask a question that poses a real problem for you, with a very specific answer, not something like "teach me how to do this".
If this answers your question don't forget to mark it as answer (the "tick" mark on the top left). A vote up would be also appreciated.
Hope it helps, regards.
Edit: I changed the code according to Thorstens Answer, using the enum, but did not work.
I am using Dependency Properties to influence a WPF control I am creating. I'm new to WPF, so I'm not sure what I am doing wrong and I can't find proper articles explaining it.
For example, I'm trying to define the Visibility of a control via Dep Properties. The property, in this case, would be this:
public static readonly DependencyProperty IconVisibilityBoldProperty =
DependencyProperty.Register("IconVisibilityBold", typeof(Visibility), typeof(RTFBox),
new PropertyMetadata(Visibility.Hidden), VisibilityValidateCallback);
private static bool VisibilityValidateCallback(object value)
{
Visibility prop = (Visibility) value;
if (prop == Visibility.Hidden || prop == Visibility.Visible)
{
return true;
}
return false;
}
public Visibility IconVisibilityBold
{
get
{
return (Visibility)GetValue(IconVisibilityBoldProperty);
}
set
{
SetValue(IconVisibilityBoldProperty, value);
}
}
Edit: for correct XAML, look for Slugarts answer.
The XAML Entry for this, in this case a ToggleButton, would be
<ToggleButton Visibility="{Binding Path=IconVisibilityBold}" ToolBar.OverflowMode="Never" x:Name="ToolStripButtonBold" Command="EditingCommands.ToggleBold" ToolTip="Bold">
<Image Source="Images\Bold.png" Stretch="None"/>
</ToggleButton>
I've output the Property, it shows as "Hidden" as the Metadata Default Value should imply, but apparently I've done something wrong with the binding. What would I have to write there?
You are trying to binding to a property of the parent control without referencing it, and it won't be set implicitly. You need to set the ElementName in the ToggleButton binding to be the name of the UserControl you are creating (giving it an x:Name property if it doesn't have one already).
<UserControl x:Name="rtfBox">
<ToggleButton Visibility="{Binding ElementName=rtfBox, Path=IconVisibilityBold}" ... />
...
</UserControl>
Also you should follow the previous answers which correctly state that the Visibility property is an enum and not a string.
The ToggleButton's Visibility property requires a value of type System.Windows.Visibility. You need to change your code to use that instead of strings:
public static readonly DependencyProperty IconVisibilityBoldProperty =
DependencyProperty.Register("IconVisibilityBold", typeof(System.Windows.Visibility), typeof(RTFBox));
public System.Windows.Visibility IconVisibilityBold
{
get
{
return (System.Windows.Visibility)GetValue(IconVisibilityBoldProperty);
}
set
{
SetValue(IconVisibilityBoldProperty, value);
}
}
So your property is a string...but it has to be a enumerable:
namespace System.Windows
{
public enum Visibility : byte
{
Visible,
Hidden,
Collapsed,
}
}
You have to bind textbox the datacontext or use it as reference to access the property correctly