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
Related
I have a reusable UserControl defined that will be used multiple times within the parent form, to represent different instances of a configured object. This UserControl has several TextBoxes representing configurable properties. For one of these properties, the value must be unique across all instances of the reusable UserControl.
My parent form utilizes these usercontrols like this:
<namespace:ReusableControl
Property1="{Binding Path=ViewModelProperty1a, Mode=TwoWay}"
Property2="{Binding Path=ViewModelProperty2a, Mode=TwoWay}"
UniqueProperty="{Binding Path=VMUniquePropertya, Mode=TwoWay}"/>
<namespace:ReusableControl
Property1="{Binding Path=ViewModelProperty1b, Mode=TwoWay}"
Property2="{Binding Path=ViewModelProperty2b, Mode=TwoWay}"
UniqueProperty="{Binding Path=VMUniquePropertyb, Mode=TwoWay}"/>
And the UserControl property looks like this:
<TextBox
x:Name="UniquePropertyTextBox"
Text="{Binding Path=UniqueProperty,
RelativeSource={RelativeSource AncestorType=local:ReusableControl},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
/>
The codebehind for the UserControl contains IDataErrorInfo validation:
public string this[string columnName]
{
get
{
string error = string.Empty;
switch (columnName)
{
case nameof(UniqueProperty):
if (!((MyViewModel)DataContext).UniquePropertiesAreUnique())
{
error = "not unique";
}
break;
//Other cases here, omitted from example
}
return error;
}
}
//-------------------------------
//Just to show the codebehind for the property:
public string UniqueProperty
{
get { return (string)GetValue(UniquePropertyDP); }
set { SetValue(UniquePropertyDP, value); }
}
public static readonly DependencyProperty UniquePropertyDP=
DependencyProperty.Register(
"UniqueProperty",
typeof(string),
typeof(ReusableControl),
new PropertyMetadata(string.Empty));
Everything appears to be wired up and bound correctly; the values update when the UI is changed as desired. If I change one of the unique property values such that it is no longer unique, I get the red border around that text box, but this is where the issue comes in - the red border only appears around the text box I just changed, not both of the instances of UniqueProperty. In the ViewModel, when either of the UniqueProperty values are changed, it triggers OnPropertyChanged for the other, but this still isn't causing the validation border to appear. If I replace OnPropertyChange with an explicit call to update the value i.e:
//In the setter for VMUniquePropertyb:
var temp = VMUniquePropertya;
VMUniquePropertya = null;
VMUniquePropertya = temp;
Then I do get the validation border to appear on both text boxes when that value is changed to match the other, and both borders disappear when either value is changed to be unique again. Of course, this is a hack, and also will cause an infinite loop if used on both properties. How can I accomplish the same result with OnPropertyChanged?
I found a solution that works for me. There may be better ways to do this, but this works fairly well.
By using the CoerceValueCallback on the DependencyProperty, we can execute code whenever the value of the property is re-evaluated, not just when it actually changes. This will fire when the PropertyChange event occurs in the ViewModel because the binding is re-evaluated. This looks like this:
public string UniqueProperty
{
get { return (string)GetValue(UniquePropertyDP); }
set { SetValue(UniquePropertyDP, value); }
}
public static readonly DependencyProperty UniquePropertyDP=
DependencyProperty.Register(
"UniqueProperty",
typeof(string),
typeof(ReusableControl),
new PropertyMetadata(string.Empty, null, UniquePropertyCoerceValueCallback));
private static object UniquePropertyCoerceValueCallback(DependencyObject d, object value)
{
((ReusableControl)d).UniquePropertyTextBox.GetBindingExpression(TextBox.TextProperty)
.UpdateTarget();
return value;
}
When the value of one of the unique properties changes and the ViewModel fires the PropertyChange event for the other unique property in the ViewModel, the DependencyProperty in the UserControl will be re-coerced, triggering this callback and updating validation.
I have used attached property in my application to hide the label,
public class LabelExtension
{
public static readonly BindableProperty ShowTimeStampProperty =
BindableProperty.CreateAttached("ShowTimeStamp", typeof(bool), typeof(LabelExtension), false);
public static bool GetShowTimeStamp(BindableObject view)
{
return (bool)view.GetValue(ShowTimeStampProperty);
}
public static void SetShowTimeStamp(BindableObject view, bool value)
{
view.SetValue(ShowTimeStampProperty, value);
}
}
View side binding is done like this,
<Label IsVisible="{Binding Path=extension:LabelExtension.ShowTimeStamp}"/>
But label is still visible how to achieve this, please anybody help me
Why don't you choose another simpler way like isVisible = "{Binding value}"
bool _value;
public bool value{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged();
}
}
An attached property is a special type of bindable property, defined
in one class but attached to other objects, and recognizable in XAML
as an attribute that contains a class and a property name separated by
a period.
It is not recommend to binding an attached property.
An attached property can define a propertyChanged delegate that will be executed when the value of the property changes, such as when the property is set on a control. In your case, you can make your label Visible or not in the propertyChanged delegate but there is no need.
You can use NotifyPropertyChanged(); as Huy Nguyen mentioned in his answer. Or you can create a model inherit from INotifyPropertyChanged and binding a property in the model to control label's visibility.
Refer : data-binding
bindable-properties
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");
}
}
I created a control, derived from Canvas, that should plot a live diagram, given values that are passed via a binding to a DependencyProperty. The simplified version is this:
public class Plotter : Canvas
{
public float Value { get { return (float)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(float), typeof(Plotter),
new PropertyMetadata(0f, new PropertyChangedCallback(ValueChangedCallBack)));
public static void ValueChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
Plotter plotter = (Plotter)property;
plotter.Value = (float)args.NewValue; //<-- Removed this line to get it to work
// Actually draw the value into the canvas geometry
plotter.PlotValue(plotter.Value);
}
}
I bound the control like this:
<mystuff:Plotter Value="{Binding MyViewModelProperty}" Height="50" Width="200" />
My ViewModel implements INotifyPropertyChanged and calls PropertyChanged correctly. If I bind MyViewModelProperty to a textbox, it correctly updates every time. Only if I bind it to my own control, my ValueChangedCallBack is only called once as the page is loaded, and then never again.
What am I not seeing here? Thanks for any help!
Solved: I dont have to set the Value explicitly in the callback.
You set the property Value on the callback for the property Value changing. That doesn't make much sense in any case. But is that locally set value overriding the binding value, causing your binding to no longer be set on the dependency property?
Do you need to set the Mode of your binding to TwoWay?
Should you not be using DependencyProperty.Register instead of DependencyProperty.RegisterAttached?
ViewModel:
public class MyViewModel
{
[Required, StringLength(50)]
public String SomeProperty { ... }
}
XAML:
<TextBox Text="{Binding SomeProperty}" MaxLength="50" />
Is there any way to avoid setting the MaxLength of the TextBox to match up my ViewModel (which could change since it is in a different assembly) and have it automatically set the max length based on the StringLength requirement?
I used a Behavior to connect the TextBox to its bound property's validation attribute (if any). The behavior looks like this:
/// <summary>
/// Set the maximum length of a TextBox based on any StringLength attribute of the bound property
/// </summary>
public class RestrictStringInputBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
AssociatedObject.Loaded += (sender, args) => setMaxLength();
base.OnAttached();
}
private void setMaxLength()
{
object context = AssociatedObject.DataContext;
BindingExpression binding = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (context != null && binding != null)
{
PropertyInfo prop = context.GetType().GetProperty(binding.ParentBinding.Path.Path);
if (prop != null)
{
var att = prop.GetCustomAttributes(typeof(StringLengthAttribute), true).FirstOrDefault() as StringLengthAttribute;
if (att != null)
{
AssociatedObject.MaxLength = att.MaximumLength;
}
}
}
}
}
You can see, the behavior simply retrieves the data context of the text box, and its binding expression for "Text". Then it uses reflection to get the "StringLength" attribute. Usage is like this:
<UserControl
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<TextBox Text="{Binding SomeProperty}">
<i:Interaction.Behaviors>
<local:RestrictStringInputBehavior />
</i:Interaction.Behaviors>
</TextBox>
</UserControl>
You could also add this functionality by extending TextBox, but I like using behaviors because they are modular.
While I'm not going to write the code out completely myself, one idea is to create your own MarkupExtension that will take the property name and reflect over looking for a StringLengthAttribute.
If the attribute exists, attempt to bind the target to that value (using reflection). If not, then bind 0 to the target value (0 is default, i.e. no max).
One way to do it would be to create a property in that same viewmodel called SomePropertyMaxLength and then bind the MaxLength property to that property.
<TextBox Text="{Binding SomeProperty}" MaxLength="{Binding SomePropertyMaxLength}"/>
The Markup extension is definitely the way to go.
I am creating a subclass of BindingDecoratorBase called Binding which has a model DataType dependency property. As MarkupExtensions are created during InitializeComponent() there is no way to determine the DataContext as it will not have been set yet.
Providing the model type permits reflective access to attributes defined on the model.
This permits:
Setting MaxLength for TextBoxes.
Setting StringFormat for TextBlocks.
Setting the default Converter depending on member data type.
Adding required validation. Using either the binding's ValidationRules or by setting ValidatesOnDataErrors.
The markup looks like:
Text="{PO:Binding DataType=model:modAccount, Path=SubAccount}"
Formatting, MaxLength, and Conversion rolled into one package with no need to change anything as the model classes change.
Or you can have your model only to accept a max # chars:
private string _MyText { get; set; }
public string MyText { get => _MyText; set => _MyText = value?.Substring(0,
Math.Min(value.Length, 15)); }
Text="{Binding Path=MyText}"