I have a control which has a default value for a property. When the control first gets its dataContext set, it assigns this property automatically.
In the xaml now, I want it to be possible to UNset this property. I've tried setting it to x:Null of just the empty string, but then I get an error because there's no converter for the property. How do I simply unassign this property from the xaml in the rare cases where I want the feature disabled?
code where it is originally set:
void OmniBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if( e.NewValue is BindingObjectBaseExtended )
{
BindingObjectBaseExtended value = (BindingObjectBaseExtended)e.NewValue;
this.SetBinding(OmniBox.ContextValidationMessagesProperty, new Binding() { Source = value, Path = new PropertyPath("ValidationMessages") });
}
}
xaml where I want to unset the property.
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="" />
</Style>
Note that if I do not set up the binding automatically when the data context changes, then by default there are no validation messages and I have to do the following in the xaml to set them up:
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="ValidationMessages" />
</Style>
What I'm trying to do is make the above binding the default for my custom OmniBox control, and allow the user to unset it or change it to something else.
DependencyProperty.UnsetValue cannot be used in XAML.
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.unsetvalue(v=vs.90).ASPX
Personally, I would create a separate dependency property, such as bool AutoBindValidation and make it default to true. If it is false, don't do anything when the DataContext changes. This is a little more self-documenting. Depending on what exactly you're trying to do, you might not want to publicly expose ContextValidationMessages at all.
If you really want to do it the way you posted, I'm not sure why setting it to {x:Null} would cause an error (unless the property type is not nullable). But this approach would have problems because DataContextChanged is going to occur after the XAML is parsed. So the user can set it to {x:Null}, but then the DataContext will change and your code will set up the default binding and trample the user's value. You could set up the binding in the control's contstructor, but then if the DataContext does not have a ValidationMessages property, your control will be spitting out binding errors.
This may be impossible, my best bet was this:
<Setter Property="ContextValidationMessages"
Value="{x:Static DependencyProperty.UnsetValue}" />
But that throws "Cannot unset setter value". So you better inverse your logic or keep the property unset another way.
I don't think there is any supported way to do this in the xaml itself. In your code you are setting a local value on the ContextValidationMessagesProperty. The Style setters you included would have a lower dependency property precedence and even if they were evaluated they would set a value based on the specified Value - not clear it. Maybe instead of setting the binding in code you could have a Setter in your default style for OmniBox that sets that property - e.g.
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
If you have to conditionally set the Binding then you could create a custom IValueConverter that checks for the specified type (passed as the parameter). e.g.
public class IsAssignableFromConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type typeParameter = parameter as Type;
if (typeParameter == null)
return DependencyProperty.UnsetValue;
return value != null && typeParameter.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then you might use it like this:
<local:IsAssignableFromConverter x:Key="isAssignableConverter" />
<Style TargetType="ui:OmniBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource isAssignableConverter}, ConverterParameter={x:Type ui:BindingObjectBaseExtended}}" Value="True">
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
</DataTrigger>
</Style.Triggers>
</Style>
In the case where you don't want this property to be applied you might set the Style for that instance of the OmniBox to a new style and make sure to set the OverridesDefaultStyle property to true.
I suppose another option is to create another dependency property that will call ClearValue on the ContextValidationMessages property but this seems like it could be a maintenance issue.
For certain cases you can 'reset' to the default value of the parent control by using a RelativeSource. For instance I'm using a DataGrid and this worked for me to reset back to the 'default'.
This is a textblock inside a datagrid cell.
<TextBlock Text="{Binding ServiceName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<!-- Change text color to purple for FedEx -->
<Trigger Property="TextBlock.Text" Value="FedEx">
<Setter Property="TextBlock.Foreground" Value="Purple"/>
</Trigger>
<!-- Reset if the cell is selected, since purple on blue is illegible -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}" Value="True">
<Setter Property="TextBlock.Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
This seems clever enough to inherit the correct color even when the window is inactive.
Related
I have a function in my UI where I want to be able to collapse/make visible a text message depending on the value of a custom property in my window object.
Using online references, I have come up with this code-behind to register the property:
public bool ValidInterval
{
get { return pValidInterval; }
}
private bool pValidInterval = true;
public static readonly DependencyProperty ValidIntervalProperty = DependencyProperty.Register("ValidInterval", typeof(bool), typeof(Settings), new UIPropertyMetadata(true));
And this corresponding XAML for the label:
<Label Name="DynamicWarning" Content="Time interval must be a valid positive integer.">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ValidInterval}" Value="true">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ValidInterval}" Value="false">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Unfortunately, this does not work. I can get it to set the parameter depending on the initial value of the property, but doesn't update the visibility dynamically like I want. I have been looking at this for an hour and what I have seems consistent with examples I am finding online for similar operations. Am I doing something wrong, or can you not update the visibility on the fly? If the latter, how do I achieve an equivalent effect?
ΩmegaMan's answer is correct. But I wanted to clarify, you don't need a backing field with a dependency property, the static dependency property IS the backing field.
public bool ValidInterval
{
get { return (bool)GetValue(ValidIntervalProperty); }
set { SetValue(ValidIntervalProperty, value); }
}
However, if you aren't in need of a dependency object specifically, then you may just want to use INotifyPropertyChanged as ΩmegaMan has it right. Dependency properties are typically used when you need to bind another property to them, such as when making your own custom control. For example Visibility itself is the dependency property in your example and ValidInterval just needs to be a normal property that invokes the NotifyPropertyChanged event.
You need for the holder class of the properties which are bound to the page, to adhere to INotifyPropertyChanged Interface and implement it.
That process informs the bound controls on the page that something has changed, and when it has changed, then the control is "notified" of the change; then it reads afresh the property it is bound to.
For WPF/Xaml they specify the seperation of data concerns for the views to business logic, is done by implementing the Model-View-ViewModel or MVVM pattern.
The link provided is dry, and there are other resources which can describe on the net, but it simply says put all your business logic that is bound from the View to a separate View Model Class; which is instantiated on your View.
I provide a basic example, any version of .Net can be used, on my blog:
MVVM Example for Easier Binding
I want to check that the ListBox element has a specific type to assign a visual style to, but the constant check fails. Maybe I'm doing it wrong?
Problem with this line:
Condition Binding="{Binding}" Value="{x:Type econemodels:DishDTOAdvance}"
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding}"
Value="{x:Type econemodels:DishDTOAdvance}" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate"
Value="{StaticResource DishNoImage}" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
The binding fails, because it binds an instance of type DishDTOAdvance and compares it with an instance of Type that describes the DishDTOAdvance type. Obviously they are different types and the condition is never true. In XAML x:Type is like typeof() or GetType() in code.
The x:Type markup extension has a similar function to the typeof() operator in C# or the GetType operator in Microsoft Visual Basic. The x:Type markup extension supplies a from-string conversion behavior for properties that take the type Type.
That is exactly the case for a custom DataTemplateSelector, no need for bindings.
Provides a way to choose a DataTemplate based on the data object and the data-bound element.
With a data template selector you can provide arbitrary logic to choose a data template for an item. In your case a switch statement for the type is enough to choose a template that can be found through FindResource in the resources up the visual tree. Of course you could also assign data templates trough properties if you do not want to search in all resources.
public class TypeTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var contentPresenter = (ContentPresenter)container;
switch (item)
{
case DishDTOAdvance _:
return (DataTemplate)contentPresenter.FindResource("DishNoImage");
// ...other type cases.
default:
return base.SelectTemplate(item, container);
}
}
}
Create and add an instance of the data template selector to your ListBox. Remove the ItemTemplate completely, it it now automatically assigned by the selector.
<ListBox ...>
<ListBox.ItemTemplateSelector>
<local:TypeTemplateSelector/>
</ListBox.ItemTemplateSelector>
<!-- ...other markup. -->
</ListBox>
The ContentControl is redundant. However in case you need it within an item template, it works just the same. ContentControl exposes an ContentTemplateSelector property for the same purpose.
Bonus round: Is the trigger impossible? No. You can create a converter that returns the type.
public class TypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Create an instance of the converter in a resource dictionary in scope.
<Window.Resources>
<local:TypeConverter x:Key="TypeConverter"/>
</Window.Resources>
Use the converter in the condition binding. Now types are compared.
<Condition Binding="{Binding Converter={StaticResource TypeConverter}}"
Value="{x:Type econemodels:DishDTOAdvance}"/>
I have a button control in a WPF application which is used for log-in purposes. The button is dual purpose: when logged-out, it shall display the text "log in". When logged-in, it shall display the text "log out".
I could do this with databinding to an appropriate string property in the viewmodel.
But it would be neater if I could simply databind to the "loggedIn" boolean (false for logged-out, true for logged-in) and then make the decision about what text to display on the button from within the view.
Is this possible?
You can achieve it using Style in Xaml without writing specific C# code.
Suppose you have IsLoggedIn property in ViewModel which is being changed on button bind command's execute method :
private void MyButtonCommandExecuteMethod()
{
this.IsLoggedIn = !this.IsLoggedIn;
}
private bool isLoggedIn;
public bool IsLoggedIn
{
get
{
return this.isLoggedIn;
}
set
{
this.isLoggedIn = value;
OnPropertyChanged(nameof(IsLoggedIn));
}
}
In the above code OnPropertyChanged is method implementation of INotifyPropertyChanged's PropertyChanged event. If you using any framework or defined own name, then replace it with appropriate name.
You could define a style such as:
<Button Command="{Binding YourButtonCommand}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content" Value="Log In" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoggedIn}" Value="True">
<Setter Property="Content" Value="Log out" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
In the above code IsLoggedIn is bind to DataTrigger to update the Button's Content property based on it's value.
Another point, the button style is inherited from default style. If you any Keyed style then use it in BasedOn Property.
Solution
Yes that is possible using value converter, specifically IValueConverter. You can bind a bool property, say BoolProperty from your viewmodel to button using converter like this.
<Button Content="{Binding BoolProperty, Converter={StaticResource BoolConverter}}" />
Step 1: You need to create a converter that will accept bool value and return whatever string as you wish.
public class BoolConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
return value.ToString();
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Step 2: Declare this converter as a resource in XAML (you will need to define local namespace alias that contains the converter in your <Window ..> start element.
<local:BoolConverter x:Key="BoolConverter" />
So now whenever property changed event is raised for BoolProperty, this converter will be triggered and button text will change appropriately.
Suggestion:
I am not sure why you think maintaining extra string property is not a clean way. It is clean and is much simpler approach than using converter. Whatever I have shown is unnecessary overhead for your case, IMO. However, since you asked about possibility, I detailed it. Choice is yours! :)
I'm trying to change the template of a ContentControl depending on the available size to the controls in it. I use a Measure() call on the ContentControl to get its desired size and then compare it to the actual size of the controls in it. If the actual size is smaller than the desired size, I want to switch to a different template. I have the actual logic down, however I experience a memory issue that I can't really explain.
The templates itself are nothing fancy for a start, just a StackPanel with some labels in it.
XAML:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource MeasurementConverter}">
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="ContentTemplate" Value="{StaticResource Template2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Code for the converter:
public class MeasurementConverter : IMultiValueConverter
{
public Object Convert(Object[] values, Type targetType, Object parameter, CultureInfo culture)
{
double actualWidth = (double?) values[0] ?? 0;
ContentControl control = (ContentControl) values[1];
var availableSize = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
control.Measure(availableSize);
var desiredSize = control.DesiredSize;
// Issue arises as soon as I use this:
var result = actualWidth < desiredSize.Width;
return result;
}
}
The thing I don't understand is that as soon as I just check if the width is smaller than the desired size (and the result is true), the application basically freezes and I see a large and constant increase in memory in the profiler until I get an OOM exception. The issue does not occur if I explicitly return true or false.
I suspect the Measure() call is the culprit and starts somehow a recursive call that results in an endless loop, but I don't get why unless it somehow updates the layout which again calls the converter.
Can anyone explain? If there is an easier method that what I have done, I'd be happy to hear it.
The issue was that changing the template caused the DataTrigger to fire again, since it is bound to the ActualWidth of the ContentControl (which changes if I change the template).
I now use a different property for DataTrigger to trigger the change of the template which isn't altered upon changing the template.
I'm having an issue when trying to do something which should be as easy as. I've attempted to use a Trigger based on a DependencyProperty or a DataTrigger - I can't get either to work.
XAML for the trigger is:
<Style x:Key="FileWatchButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Main:Main.XmlFilesAvailableForLoading" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
And the associated code-behind is:
public static readonly DependencyProperty XmlFilesAvailableForLoadingProperty =
DependencyProperty.Register("XmlFilesAvailableForLoading", typeof(bool), typeof(Main));
public bool XmlFilesAvailableForLoading
{
get
{
try
{
return (bool)this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Windows.Threading.DispatcherOperationCallback)delegate { return GetValue(XmlFilesAvailableForLoadingProperty); },
XmlFilesAvailableForLoadingProperty);
}
catch (Exception)
{
return (bool)XmlFilesAvailableForLoadingProperty.DefaultMetadata.DefaultValue;
}
}
set
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Threading.SendOrPostCallback)delegate{ SetValue(XmlFilesAvailableForLoadingProperty, value); }, value);
}
}
Basically the dp is being set correctly by the presenter (it's based on a FileSystemWatcher class looking for one or more files) but the Trigger is not being fired. Is this a threading issue?
Thanks.
It's not clear if the code is complete, but it looks like the Property path in your trigger may be wrong. Does the button being styled have a Main property? I am guessing not; it looks like you are trying to trigger on a property of a different element, called Main -- is that right?
In any case, the namespace prefix is not required. If the button has a property named Main, then you can address this directly; if it doesn't, then the prefix won't help you.
My guess is that you probably need a DataTrigger whose binding refers to the Main element:
<local:Main Name="MyMain" ... /> <!-- this has the XmlFilesAvailableForLoading property -->
<DataTrigger Binding="{Binding XmlFilesAvailableForLoading, ElementName=MyMain}"
Value=True>
<Setter Property="Background" Value="Red" />
</DataTrigger>
On an unrelated note, you should have any non-boilerplate implementation in your DP getter and setter. Remember that the binding and styling system will bypass the getter and setter and talk directly to the underlying storage. So I'd strongly advise changing these back to just plain GetValue and SetValue calls.