How to bind a binary property to a property with multiple values? - c#

I have a ToggleButton being a standard WPF class and I want to bind IsChecked to a property status of my model and the Status can have more than 2 values: Status1, Status2, Status3, Status4. The type of Status is SomeThirdPartyClassStatus and I don't have access to its source code.
<ToggleButton IsChecked="{Binding Status???}" />
So how can I bind Status to IsChecked then? I prefer a xaml solution.
I want to bind IsChecked property so that it's True when the Status is equal to Status1, and it's false in other cases. I prefer not to write any code in a *.cs file, but only xaml code.

As an alternative you can use pure XAML solution with DataTrigger. Assuming that you have something like
public enum SomeThirdPartyClassStatus {
Status1,
Status2,
Status3,
Status4
}
you can do
<ToggleButton>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="IsChecked" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:SomeThirdPartyClassStatus.Status1}">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Where local is namespace for SomeThirdPartyClassStatus like
xmlns:local="clr-namespace:WpfApplication1"
Caveat is that it will work one-way only
EDIT
For two-way binding you'll need custom IValueConverter
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (SomeThirdPartyClassStatus)value == SomeThirdPartyClassStatus.Status1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool?)value == true ? SomeThirdPartyClassStatus.Status1 : SomeThirdPartyClassStatus.Status2;
}
}
and then the binding would look like
<ToggleButton IsChecked="{Binding Path=Status, Converter={StaticResource EnumConverter}}"/>

Create a class derived from IValueConverter which you apply to your binding.
https://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter(v=vs.110).aspx

Update:
Simply use this style for toggle button
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Status1" >
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
So if ever Status has value Status1 toggle button will be checked.

Related

InvalidCastException while binding with converter

I have a problem with a binding, I want to change an icon when the column has at least one element filtered. But it is not working.
This is my converter:
public class FilteredToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int index = ((DataGridColumnHeader)((MahApps.Metro.IconPacks.PackIconMaterial)value).TemplatedParent).Column.DisplayIndex;
return !((AdvancedDataGrid)parameter).FilterLists[index].Any(item => item.NotFiltered);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
A filter for each column is generated with a contentTemplate, and I have 2 problems, If I set this style in controlTemplate.Resources the dataGrid reference is not found.
If I set the style in the DataGrid.Resources then I get
InvalidCastException: Can not convert an object from type 'System.Windows.Controls.DataGrid' to type 'MahApps.Metro.IconPacks.PackIconMaterial'.
This is the style:
<Style TargetType="iconPacks:PackIconMaterial">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource FilteredToIcon}, ConverterParameter={x:Reference dataGrid}}" Value="True">
<Setter Property="Kind" Value="FilterMenu"/>
</DataTrigger>
</Style.Triggers>
</Style>
And this is a summary of the whole XAML of my custom AdvancedDataGrid:
<DataGrid x:Class="ZOT.GUI.Items.AdvancedDataGrid"
x:Name="dataGrid"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:ZOT.GUI.Items">
<DataGrid.Resources>
<local:FilteredToIconConverter x:Key="FilteredToIcon" />
<!-- The style can be here-->
</DataGrid.Resources>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<ControlTemplate.Resources>
<!-- The style can be here as well, whatever that works-->
</ControlTemplate.Resources>
<Grid>
<ContentPresenter/>
<ToggleButton x:Name="Y">
<!-- This is the Icon I want to change -->
<iconPacks:PackIconMaterial Kind="Filter"/>
</ToggleButton>
<Popup x:Name="pop" Width="auto" IsOpen="{Binding IsChecked, ElementName=Y,Mode=TwoWay}">
<!-- .... -->
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
If you have any clue, tell me please.
Thank you.
If you want to bind to the PackIconMaterial itself, you should set the RelativeSource property of the binding to RelativeSource.Self:
<Style TargetType="iconPacks:PackIconMaterial">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource FilteredToIcon}, ConverterParameter={x:Reference dataGrid},
RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Kind" Value="FilterMenu"/>
</DataTrigger>
</Style.Triggers>
</Style>
That's the only way you will be able to cast value to MahApps.Metro.IconPacks.PackIconMaterial in the converter.
There are probably better ways of solving whatever you're trying to do though.

Value Converter set in Style does not get file value, but whole datarow

I set up a style for a label which should be triggered if the value of the bound filed in the underlying DataTable is greater than zero:
<c:Groesser0BooleanValueConverter x:Key="G0" />
<Style x:Key="DashboardProzent" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource G0}}" Value="{x:Null}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
The label itself is set up this way (a DataTemplate in a resource Dictionary):
<Label Content="{Binding percentCol}" Style="{StaticResource DashboardPrzoent}" Grid.Row="0" Grid.Column="2"/>
The converter looks like this:
public class Groesser0BooleanValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (System.Convert.ToInt32(value) > 0)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
If a run the whole thing, i get the error in the Convert method of the Groesser0BooleanValueConverter class:
Unable to cast object of type 'System.Data.DataRowView' to type
'System.IConvertible'
If I check the parameter of the Convert method of the Converter, it shows that a System.Data.DataRowView has been passed, instead of the value of the percentCol field, which I would have expected. How can I get the Label to pass the value instead of the whole row?
Update:
If I set the Label to
<Label Content="{Binding Path=percentCol}" Style="{StaticResource DashboardPrzoent}" Grid.Row="0" Grid.Column="2"/>
the error still occurs. If I disable the trigger, the value is shown (even without the Path= segment.
I don't want to state the field name in the style segment, as I would like to use it for other values to.
First I think the main problem is due to your Binding in the DataTrigger
<DataTrigger Binding="{Binding Converter={StaticResource G0}}" Value="{x:Null}">
This will pass on the direct DataContext of your Label to your Converter, which is obviously not what you want.
You should have:
<DataTrigger Binding="{Binding Converter={StaticResource G0}, Path=percentCol}" Value="False">
I also corrected the Value part, since your 'Converter' only sends back True/False. If you want to change back to Value="{x:Null}", you will have to modify your converter.
I would also advise to be safer in your Converter, otherwise the explicit conversion System.Convert.ToInt32(value) may (will...) raise an error.
I wanted to set the Binding Path and the Binding Converter in two different locations, for example a DataTemplate and a Style file.
This can be done by using {Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource converterName}} in the Style and setting the Binding Path as usual in the DataTemplate
Converter
<Style x:Key="DashboardPrzoent" TargetType="{x:Type Label}">
...
<Style.Triggers>
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource G0}}" Value="True">
<Setter Property="Foreground" Value="{StaticResource Gruen}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content, RelativeSource={RelativeSource Self}, Converter={StaticResource G0}}" Value="False">
<Setter Property="Foreground" Value="{StaticResource Rot}"/>
</DataTrigger>
</Style.Triggers>
</Style>
DataTemplate
<Label Content="{Binding Path=percentCol}" ... />

Converting RowStyle values Conditionally

So I don't know if this is really possible, but it is worth a shot. I have two different DatagridRowStyles that I want to set in the DataGridBaseStyle Conditionally.
For example:
<Style x:Key="DataGridBaseStyle"
TargetType="sdk:DataGrid">
<Setter Property="CellStyle" Value="{StaticResource DataGridCellBaseStyle}" />
<Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridColumnHeaderBaseStyle}" />
<Setter Property="RowHeaderStyle" Value="{StaticResource DataGridRowHeaderBaseStyle}" />
<Setter Property="RowStyle" Value="{StaticResource DataGridRowBaseStyle} />
...
The property setter value of RowStyle I want to use one of two Styles depending on what "Custom Theme" I am using.
So far I tried setting it this way, but it only defaults to the normal style.
<Setter Property="RowStyle">
<Setter.Value>
<Binding>
<Binding.Converter>
<conv:DataGridRowStyleConverter/>
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
And the same with
<Setter Property="RowStyle" Value="{StaticResource DataGridRowBaseStyle} Converter={StaticResource DataGridRowStyleConverter}" />
Converter Coding
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (ColourScheme == "Dark")
return Application.Current.Resources["DataGridRowBaseDarkStyle"] as Style;
return Application.Current.Resources["DataGridRowBaseStyle"] as Style;
}
I am kind of thinking that my converter return values are incorrect, but I also did play around with those values with no luck.
Provided that ColorScheme is a public property in the current DataContext, the Setter should look like this:
<Setter Property="RowStyle"
Value="{Binding ColorScheme, Converter={StaticResource DataGridRowStyleConverter}}"/>
or like this in XML tag syntax:
<Setter Property="RowStyle">
<Setter.Value>
<Binding Path="ColorScheme"
Converter="{StaticResource DataGridRowStyleConverter}"/>
</Setter.Value>
</Setter>
The Converter would get the current value of the ColorScheme by the value argument of the Convert method:
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString == "Dark"
? Application.Current.Resources["DataGridRowBaseDarkStyle"] as Style
: Application.Current.Resources["DataGridRowBaseStyle"] as Style;
}

WPF Custom Control "style model"

I have custom control in wpf which change his look based on one Property:
...
<Grid>
<Rectangle Fill="[Something]" />
</Grid>
In code i have the property AlarmLevel, when AlarmLevel is bigger than 5 I want the fill to be red, otherwise blue.
How can I do this. (I don't want the fill property to be exposed)
Tnx
Since you're basing your fill value on an inequality, you could do this a couple of ways.
The recommended way is probably to use a converter on your binding to make it into a boolean value. Then use a data trigger to set the fill value based on whether the value is true or false, like so:
<Rectangle>
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding AlarmLevel, Converter={StaticResource AlarmLevelConverter}}" Value="True">
<Setter Property="Fill">
<Setter.Value>
<SolidColorBrush Color="Red" />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding AlarmLevel, Converter={StaticResource AlarmLevelConverter}}" Value="False">
<Setter Property="Fill">
<Setter.Value>
<SolidColorBrush Color="Black" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Your converter could look something like (perhaps with more exception handling):
public class AlarmLevelConverter: IValueConverter {
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return ((int)(value) > 5);
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotSupportedException();
}
}
Don't forget you'll need to add a reference to the converter class as a resource on your user control:
If you wanted to forego the converter method, you could also create a "helper" boolean property in your data context called something like "IsAlarming". It would look something like:
public bool IsAlarming {
get { return AlarmLevel > 5; }
}
You would then bind your data trigger to IsAlarming rather than AlarmLevel. This isn't recommended though, because it's not pure MVVM.

How to implement NullText in a TextBlock with Binding?

I would like to implement a "NullText" behavior for a TextBlock that is bound to a property in a ViewModel. When that property in the ViewModel is null or empty, I would like to display gray italic text something like "No Data". I'd like this to follow MVVM pattern but I am lost...
Update
So after playing around with the solution James Webster suggested, I got it to work like this...
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<c:NullOrEmptyValueConverter x:Key="NullOrEmptyValueConverter" Text="(No Data)"/>
</UserControl.Resources>
...
<TextBlock Name="SerialNumberTextBlock" Text="{Binding Path=SerialNumber, Converter={StaticResource NullOrEmptyValueConverter}}">
<TextBlock.Resources>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="(No Data)">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Resources>
</TextBlock>
I'd recommend implementing an IValueConverter; if the source value is not null or empty, then pass it through to the TextBlock. If the source value is null or empty, then render your chosen text.
public class NullValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = (string)value;
if (str.IsNullOrWhitespace())
{
return "No Data";
}
return str;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
... //An empty implementation I expect...
}
}
However I have just realised that you want to set the style as well... hmmm, probably a DataTrigger that sets the style if the value is 'No Data' required I expect;
<TextBlock Text="{Binding Path=SomeProperty, Converter={StaticResource keyToNullValueConverter}">
<TextBlock.Triggers>
<DataTrigger Binding="{Binding Path=Text}" Value="No Data">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</TextBlock.Triggers>
</TextBlock>
Something along those lines might work.
I think you don't need to create Converter Class, you can simply write your style code like this.
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SerialNumberTextBlock, Path=Text}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
</Style.Triggers>
</Style>
Note :- You need to include the system namespace as
xmlns:System="clr-namespace:System;assembly=mscorlib"
You could try to bind to a property that looks thus
private string _textBlockText;
public string TextBlockText
{
get
{
if(string.IsNullOrEmpty(_textBlockText))
{
return "No Data";
}
return _textBlockText;
}
set
{
_textBlockText = value;
}
}
and then use the XAML that James has mentioned for styling. Saves the need for a converter.
Very late to the party, but here is my answer.
<TextBox >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding MyText, TargetNullValue=No data}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyText}" Value="{x:Null}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<DataTrigger Binding="{Binding MyText}" Value="{x:Static System:String.Empty}">
<Setter Property="FontStyle" Value="Italic"/>
</DataTrigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Text" Value="{Binding MyText}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
My answer assumes that you want the default text to disappear on focus, which is more in line how inputs with default text usually behave. Also this works only for null values. If you need a check for string empty or additional logic, you could instead use a converter like in other provided answers. The key idea is, that you remove null value text or converter in the binding when control gains focus and thus only display the default text when control has no focus. This also prevents the default value flowing back to your view model.
DataTriggers part for font style was kindly borrowed from pchajer's answer.

Categories

Resources