"Single binding" works but not MultipleBinding with same parameters - wpf

Im using Bindings to change Style on a button. To be able to do this Ive found that I need to use MultipleBinding. But for some reason this doesnt work.
Here is the code in my UserControl (which inherits from a uc-base-class):
<CtrlLib:UcBaseCounter
...
x:Name="mySelf">
<CtrlLib:UcBaseCounter.Resources>
<ResourceDictionary>
...
<CtrlLib:StyleConverter x:Key="styleConverter" />
</ResourceDictionary>
</CtrlLib:UcBaseCounter.Resources>
<Grid Margin="2">
<Button Click="Ctrl_Click">
<Button.Style>
<MultiBinding Converter="{StaticResource styleConverter}">
<MultiBinding.Bindings>
<Binding ElementName="mySelf"/>
<Binding Path="HealthStatus"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.Style>
But this works great:
<TextBlock Text="{Binding ElementName=mySelf, Path=HealthStatus}" />
I get this warning in the Output window during runtime:
System.Windows.Data Warning: 40 : BindingExpression path error: 'HealthStatus' property not found on 'object' ''MyLayoutViewModel' (HashCode=3696098)'. BindingExpression:Path=HealthStatus; DataItem='MyLayoutViewModel' (HashCode=3696098); target element is 'Button' (Name='Component'); target property is 'Style' (type 'Style')
Why cant HealthStatus be found in the MultipleBinding when it is obviously there?
I dont include the code for the converter since it doesnt seem to be the problem. HealthStatus is declared as enum but since it is working for the "singlebinding" and the error-message only says it cant find it I dont think thats the problem either.
Thanx!
Added:
This is my Converter:
public class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
HealthStatus.Statuses status;
status = ((UcBase)targetElement).HealthStatus;
Style newStyle;
switch (status)
{
case HealthStatus.Statuses.Error:
newStyle = (Style)targetElement.TryFindResource("RedStyle");
return newStyle;
...
}
return (Style)targetElement.TryFindResource("WhiteStyle");
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the styles are declared in a resourcedictionary:
<Style x:Key="WhiteStyle" TargetType="Button">
HealthStatus is declare in my UcBase class that I use as a base class for my UserControls:
public class UcBase : UserControl
{
public static readonly DependencyProperty HealthStatusProperty = DependencyProperty.Register("HealthStatus", typeof(HealthStatus.Statuses), typeof(UcBase));
public HealthStatus.Statuses HealthStatus
{
get { return (HealthStatus.Statuses)this.GetValue(HealthStatusProperty); }
set { this.SetValue(HealthStatusProperty, value); }
}

The two bindings you showed do different things.
<TextBlock Text="{Binding ElementName=mySelf, Path=HealthStatus}" />
Looks for a property "HealthStatus" on the control "mySelf"
While
<MultiBinding Converter="{StaticResource styleConverter}">
<MultiBinding.Bindings>
<Binding ElementName="mySelf"/>
<Binding Path="HealthStatus"/>
</MultiBinding.Bindings>
</MultiBinding>
Looks for 2 values: "mySelf" which is a Control and the "HealthStatus" property.
Since you didn't clarify where to look for the last one, it will look in the DataContext for a property called HealthStatus!
Then once it got those 2 values, it will convert them using your IMultiValueConverter, that should return a Style
Now i'm just guessing at what you really want to achieve, but if you want to bind to the "HealthStatus" property, you will need to change the MultiBinding to:
<MultiBinding Converter="{StaticResource styleConverter}">
<MultiBinding.Bindings>
<Binding ElementName="mySelf"/>
<Binding Path="HealthStatus" ElementName="mySelf"/>
</MultiBinding.Bindings>
</MultiBinding>
EDIT: To answer the comment, you couldn't do this with a simple converter, because you need to know the UserControl where the style you want to apply resides, otherwise how will you find it?
I guess in your converter you decide which style to use based on "HealthStatus" then you use the FindResource("Resource_Name") method to fetch it.

Related

wpf dynamic user security

The software uses a SmartSnowUser object, which contains a SecurityRole object. The client needs SecurityRole to be customizable, so it has a list of enum SecurityTasks, which the clients can add/remove from. Controls should only be visible if their given SecurityTask exists in the current SmartSnowUser's
SecurityRole.
With this setup, I am struggling to get all the functionality I need.
I need the ability to:
change control visibility based on whether CurrentUser.Role contains GivenTask
make control visibility more granular when necessary (e.g. Visibility &= isInEditMode)
meet the previous two requirements without having to create a separate style for each color/task/extra-qualifier combination
Here are the two main approaches I've tried.
Attempt #1:
Wpf User Security Strategy
Current issue: visibility is not being triggered; breakpoint in Convert() method is never hit
Long-term issue: Uses style, so every other custom style will need to be BasedOn this default; also, have to duplicate functionality for editability
Code:
/**Style.xaml**/
<local:TagToVisibilityConverter x:Key="TagToVisibilityConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TagToVisibilityConverter}">
<Binding Path="MainData.CurrentUser"/>
<Binding RelativeSource="{RelativeSource Mode=Self}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
/**Style.xaml.cs**/
public class TagToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length >= 2 && (values[1] as FrameworkElement).GetValue(SecurityLevel.RequiredTaskProperty) is SecurityTask requiredTask)
{
//If element has a task assigned and user is not logged in, do not show
if (values[0] is SmartSnowUser currentUser && currentUser.Role != null)
{
return currentUser.Role.Tasks.Contains(requiredTask) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
//If element has no task assigned, default to visible
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SecurityLevel
{
public static readonly DependencyProperty RequiredTaskProperty = DependencyProperty.RegisterAttached("RequiredTask", typeof(SecurityTask), typeof(FrameworkElement), new PropertyMetadata(SecurityTask.ControlBasic));
public static void SetRequiredTask(UIElement element, SecurityTask value)
{
element.SetValue(RequiredTaskProperty, value);
}
public static SecurityTask GetRequiredTask(UIElement element)
{
return (SecurityTask)element.GetValue(RequiredTaskProperty);
}
}
/**Implementation in User Control**/
<Button Name="BtnNew" Content="Create New Role" Style="{StaticResource ButtonBlue}" server:SecurityLevel.RequiredTask="{x:Static enums:SecurityTask.EditRoles}" />
Attempt #2:
How to extend instead of overriding WPF Styles
How to add dependency property to FrameworkElement driven classes in WPF?
Attempted to merge these two solutions into one. Set the Tag value to a SecurityTask, then use trigger to set visibility
issue: cannot set visibility at a more granular level without a style (e.g. cannot set 'Visibility' property directly in control); cannot distinguish between visibility/editability
Code:
/**Style.xaml**/
<!--#region Visibility-->
<!-- Default frameworkElement style definition -->
<local:TagToVisibilityConverter x:Key="TagToVisibilityConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource TagToVisibilityConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
<Binding Path="MainData.CurrentUser"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<!-- Extending default style -->
<Style x:Key="ButtonBasic" TargetType="Button" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Setter Property="Background" Value="{StaticResource BrushGreyDark}" />
<Setter Property="Foreground" Value="{StaticResource BrushWhite}" />
</Style>
<Style x:Key="ButtonBlue" TargetType="Button" BasedOn="{StaticResource ButtonBasic}">
<Setter Property="Background" Value="{StaticResource BrushBlue}" />
</Style>
/**Style.xaml.cs**/
public class TagToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length >= 2 && values[0] is SecurityTask requiredTask)
{
//If element has a task assigned and user is not logged in, do not show
if (values[1] is SmartSnowUser currentUser && currentUser.Role != null)
{
return currentUser.Role.Tasks.Contains(requiredTask) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
//If element has no task assigned, default to visible
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
/**Implementation in User Control**/
//This button works great. Exactly what I need.
<Button Name="BtnNew" Content="Create New Role" Style="{StaticResource ButtonBlue}" Tag="{x:Static enums:SecurityTask.EditRoles}" />
//This button does not work, because the newly set Visibility property overrides the style.
<Button Name="BtnEdit" Content="Edit Role" Style="{StaticResource ButtonBlue}" Tag="{x:Static enums:SecurityTask.EditRoles}" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBoolToVisibilityConverter}}" />
Attempt #2 ALMOST works. It's that last stinking button, BtnEdit. It is far too cluttered to create a new style - BasedOn ButtonBlue, which is BasedOn ButtonDefault, which is BasedOn our original up there - every time I need to add another qualifier to my visibility setting.
I seem to be over-complicating this. Is there a cleaner approach to what I'm trying to do?

FallBackValue in IMultiValueConverter is UnsetValue

I am trying to use a single IMultiValueConverter for more than one controls in XAML.
I am using a simple string Literal to tell what value the IMultiValueConverter is supposed to return.
But I am getting DependencyProperty.UnsetValue in values[2],ie value of parametter named Command when it comes to convert function of ModifierCategoryEnableDisable.
Similar arrangment is working on similar controls on this XAML form within other IMultiValueConverters but not here.
Please guide what am i missing?
NOTE:
CurrentRec is the currently selected object from the ViewModel
DM_CategoryData is a Class and Current_Selected_Category is a List<DM_CategoryData> in the ViewModel's current object,ie CurrenRec.
XAML:
<GroupBox Width="226" Height="117" Margin="0" Canvas.Top="252" Header="Modifiers" Canvas.Left="55" >
<GroupBox.IsEnabled>
<MultiBinding Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" Converter="{StaticResource MDNS}">
<Binding Path="SearchFound" />
<Binding Path="CurrentRec.Current_Selected_Category"/>
<Binding Path="Command" FallbackValue="1" />
</MultiBinding>
</GroupBox.IsEnabled>
</GroupBox>
C#:
public class ModifierCategoryEnableDisable : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string Command = values[2].ToString();
bool Retval1 = false;
string Retval2 = "";
switch(Command)
{
case "1":
bool SearchFound = (bool)values[0];
DM_CategoryData CurrentSelectedItemCategory = (DM_CategoryData)(values[1]);
Retval1 = SearchFound && (CurrentSelectedItemCategory == null ? true : CurrentSelectedItemCategory.IsModifier.Equals("1") ? false : true);
break;
case "2":
Retval2 = "0";
break;
}
if(Command.Equals("1"))
{
return Retval1;
}
else
{
return Retval2;
}
}
}
In order to provide additional static data to a multibinding converter, use a ConverterParameter:
<MultiBinding Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" Converter="{StaticResource MDNS}" ConverterParameter="1">
<Binding Path="SearchFound" />
<Binding Path="CurrentRec.Current_Selected_Category"/>
</MultiBinding>
And check the parameter in the Convert method:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string Command = parameter as string;
// ...
}
You are trying to set the fallbackvalue for GroupBox.IsEnabled property and it is a bool type. But you are setting the value as 1. So only Values[2] returns the UnsetValue. Try to set bool value as Fallbackvalue.

Binding a bool property to a bool expression [duplicate]

I have a WPF control that has a Message property.
I currently have this:
<dxlc:LayoutItem >
<local:Indicator Message="{Binding PropertyOne}" />
</dxlc:LayoutItem>
But i need that Message property to be bound to two properties.
Obviously can't be done like this, but this can help explain what it is I want:
<dxlc:LayoutItem >
<local:Indicator Message="{Binding PropertyOne && Binding PropertyTwo}" />
</dxlc:LayoutItem>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
Try use the MultiBinding:
Describes a collection of Binding objects attached to a single binding target property.
Example:
XAML
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource myNameConverter}"
ConverterParameter="FormatLastFirst">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Converter
public class NameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string name;
switch ((string)parameter)
{
case "FormatLastFirst":
name = values[1] + ", " + values[0];
break;
case "FormatNormal":
default:
name = values[0] + " " + values[1];
break;
}
return name;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string[] splitValues = ((string)value).Split(' ');
return splitValues;
}
}
You can't do And operation in XAML.
Create wrapper property in your view model class which will return and of two properties and bind with that property instead.
public bool UnionWrapperProperty
{
get
{
return PropertyOne && PropertyTwo;
}
}
XAML
<local:Indicator Message="{Binding UnionWrapperProperty}" />
Another approach would be to use MultiValueConverter. Pass two properties to it and return And value from the converter instead.

Multibinding not working on TextBox.Text

I have a MultiBinding that is not working on TextBox.Text. I have the same code that is binding properly to Value of Extended WPF Toolkit's IntegerUpDown.
It is going through an IMultiValueConverter that takes the bound POCO and the listbox it is part of (it is displaying the order of the item in the listbox)
Here is the code:
<!--works-->
<wpf:IntegerUpDown ValueChanged="orderChanged" x:Name="tripOrder">
<wpf:IntegerUpDown.Value>
<MultiBinding Converter="{StaticResource listBoxIndexConverter}" Mode="OneWay">
<Binding />
<Binding ElementName="listTrips" />
</MultiBinding>
</wpf:IntegerUpDown.Value>
</wpf:IntegerUpDown>
<!--doesn't work-->
<TextBox x:Name="tripOrder2">
<TextBox.Text>
<MultiBinding Converter="{StaticResource listBoxIndexConverter}" Mode="OneWay">
<Binding />
<Binding ElementName="listTrips" />
</MultiBinding>
</TextBox.Text>
</TextBox>
Here is the result:
I don't believe it is relevant, but just in case, here is the class that performs the conversion:
public class ListBoxIndexConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var trip = values[0] as TripBase;
if (trip == null)
{
return null;
}
var lb = values[1] as CheckListBox;
if (lb == null)
{
return null;
}
//make it 1 based
return lb.Items.IndexOf(trip) + 1;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
The converter should return the type that the property expects. The reason is that in regular use of the properties (i.e. without Binding), the properties may have type converters that convert from one type (or more) to the type required by the property. For example, when you write:
<ColumnDefinition Width="Auto"/>
there's a converter that converts string "Auto" to:
new GridLength(1, GridUnitType.Auto)
When using binding, this mechanism is bypassed since the converter should return the right type.
So, to fix your issue, at the return of your converter:
return (lb.Items.IndexOf(trip) + 1).ToString();
This should fix the TextBox.
Now, for the IntegerUpDown. It sounds like it actually expects to receive an int and returning a string will break it. So, again, change the return of the converter:
if (targetType == typeof(int))
{
return lb.Items.IndexOf(trip) + 1;
}
else if (targetType == typeof(string))
{
return (lb.Items.IndexOf(trip) + 1).ToString();
}
else
{
throw new NotImplementedException(String.Format("Can not convert to type {0}", targetType.ToString()));
}
The binding is not going to work, because the listTrips is not changing when the list box's selected value changes. The thing that changes is listTrips.SelectedItem, so you should bind against it:
<Binding Path="SelectedItem" ElementName="listTrips"/>
Actually, I wonder why it works for the first example.

Binding for WPF Styles

I'm trying to create a custom control - a button - which will have multiple styles applied to it depending on the value of a property within the data context.
What I was thinking is using something similar to:
<Button Style="{Binding Path=ButtonStyleProperty, Converter={StaticResource styleConverter}}" Text="{Binding Path=TextProp}" />
And in code... Implement an IValueConverter which does something similar to the code below in the ConvertTo method:
switch(value as ValueEnums)
{
case ValueEnums.Enum1:
FindResource("Enum1ButtonStyle") as Style;
break;
... and so on.
}
However I'm not entirely sure about how to pull out the style object and even if this is possible at all...
What I am doing in the mean time is handling the DataContextChanged event, then attaching a handler to the PropertyChanged event of the object being bound to the button - then running the switch statement in there.
Its not quite perfect but until I can find a better solution it seems like that is what I'll have to use.
If you want to replace the whole style (rather than just elements of it) then you'll probably be storing those styles in resources. You should be able to do something along the lines of:
<Button>
<Button.Style>
<MultiBinding Converter="{StaticResource StyleConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="MyStyleString"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.Style>
</Button>
By using a MultiBinding and using Self as the first binding we can then lookup resources in our converter. The converter needs to implement IMultiValueConverter (rather than IValueConverter) and can look something like this:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
string styleName = values[1] as string;
if (styleName == null)
return null;
Style newStyle = (Style)targetElement.TryFindResource(styleName);
if (newStyle == null)
newStyle = (Style)targetElement.TryFindResource("MyDefaultStyleName");
return newStyle;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
It's not something I do very often, but that should work from memory :)
It seems that you need to use DataTrigger class. It allows you to apply different styles to your button based on it's content.
For example following style will change button's background property to red based on value of data context object's property
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path="Some property"}"
Value="some property value">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
For those of us who can't use multi value converter (I'm looking at you SL4 and WP7:), thanks to Steven's answer I found a way using an ordinary value converter.
The only assumption is the style value is contained within the property of the style being set.
So if you're using the MVVM pattern then the style value (such as TextSmall, TextMedium, TextLarge) is assumed to be part of the view model, and all you have to do is pass the converter parameter defining the name of style.
For example, say your view model has property:
public string ProjectNameStyle
{
get { return string.Format("ProjectNameStyle{0}", _displaySize.ToString()); }
}
Application style:
<Application.Resources>
<Style x:Key="ProjectNameStyleSmall" TargetType="TextBlock">
<Setter Property="FontSize" Value="40" />
</Style>
<Style x:Key="ProjectNameStyleMedium" TargetType="TextBlock">
<Setter Property="FontSize" Value="64" />
</Style>
<Style x:Key="ProjectNameStyleLarge" TargetType="TextBlock">
<Setter Property="FontSize" Value="90" />
</Style>
XAML view:
<TextBlock
Text="{Binding Name}"
Style="{Binding ., Mode=OneWay, Converter={cv:StyleConverter}, ConverterParameter=ProjectNameStyle}">
With your StyleConverter class implementing IValueConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Style))
{
throw new InvalidOperationException("The target must be a Style");
}
var styleProperty = parameter as string;
if (value == null || styleProperty == null)
{
return null;
}
string styleValue = value.GetType()
.GetProperty(styleProperty)
.GetValue(value, null)
.ToString();
if (styleValue == null)
{
return null;
}
Style newStyle = (Style)Application.Current.TryFindResource(styleValue);
return newStyle;
}
Note that this is WPF code, as the converter is derived from a MarkupExtension as well as IValueConverter, but it will work in SL4 and WP7 if you use static resource and add a bit more leg work as the TryFindResource method doesn't exist.
Hope that helps someone, and thanks again Steven!
ViewModel
private Style _dynamicStyle = (Style)Application.Current.FindResource("Style1");
public Style DynamicStyle
{
get { return _dynamicStyle; }
set
{
_dynamicStyle = value;
OnPropertyChanged("DynamicStyle");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implement a property in your ViewModel and then dynamically change style where ever you want like below.
DynamicStyle=(Style)Application.Current.FindResource("Style2");// you can place this code where the action get fired
View
Then set DataContext value and then implement the following code in your view
<Button Style="{Binding DynamicStyle,Mode=TwoWay}"/>

Resources