WPF Custom Control "style model" - c#

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.

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.

Convert y / n to true / false in XAML

I used the following code to preselect some rows in a listbox:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding f_selected, Mode=OneWay}" />
</Style>
</ListBox.ItemContainerStyle>
The value of f_selected in the code can be only true or false, but on the DB the values are y/n.
I've used a trick to convert y/n to true/false by using an object, but higher heads have asked me to work only with y/n in the objects.
Is there any way to work with a string instead of a bool or to do the conversion in the XAML or the viewmodel?
Thanks for the help and as always sorry for the bad english.
In WPF you could use a DataTrigger:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding f_selected}" Value="y">
<Setter Property="IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
In UWP you could accomplish the same thing using a DataTriggerBehavior.
Note: mm8's answer using DataTrigger's is great if you're developing a WPF application.
In WPF and UWP, you can create a custom implementation of the IValueConverter interface to achieve this with almost the same code. Basically, it will transform your string input to a boolean depending on the rules you define.
WPF:
public class StringToBooleanConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString().Equals("y");
}
// This is not really needed because you're using one way binding but it's here for completion
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value is bool)
{
return value ? "y" : "n";
}
}
}
UWP:
The above code is exactly the same except for the last parameter in Convert and ConvertBack methods:
public object Convert(object value, Type targetType, object parameter, string language) { }
public object ConvertBack(object value, Type targetType, object parameter, string language) { }
The following is more or less the same for both WPF and UWP. You can use the converter in XAML to convert from string to bool:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding f_selected, Mode=OneWay, Converter={StaticResource StringToBooleanConverter}}" />
</Style>
</ListBox.ItemContainerStyle>
Also, remember to introduce the converter in the beginning:
<Window.Resources>
<local:YesNoToBooleanConverter x:Key="StringToBooleanConverter" />
</Window.Resources>

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;
}

How to bind a binary property to a property with multiple values?

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.

Categories

Resources