WPF: unique style for each combo box item bound to dictionary - wpf

A ComboBox bound to the dictionary (of Enum, String). Selected value path is dictionary's Key.
Is it possible to set individual style for each combo box item in XAML?

In the following I use a custom enum called Cards, that has constants Skull, Hearts and others for demonstration purposes. You can simply use your enum type instead.
Item Container Style Using Data Triggers
You could create an items container style with triggers for each enum value.
<Style x:Key="EnumComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Key}" Value="{x:Static local:Cards.Skull}">
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Key}" Value="{x:Static local:Cards.Hearts}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
<ComboBox ItemContainerStyle="{StaticResource EnumComboBoxItemStyle}" ...>
Item Container Style Selector
Another option is to create a style selector if you have multiple distinct styles like this:
<Style x:Key="SkullComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style x:Key="HeartsComboBoxItemStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Red"/>
</Style>
<!-- ...other styles. -->
The style selector determines the style based on the enum value.
public class CardsKeyStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
if (container is FrameworkElement element && item is KeyValuePair<Cards, string> keyValuePair)
{
switch (keyValuePair.Key)
{
case Cards.Skull:
return element.FindResource("SkullComboBoxItemStyle") as Style;
case Cards.Hearts:
return element.FindResource("HeartsComboBoxItemStyle") as Style;
// ...other cases.
}
}
return null;
}
}
You can assign the style selector to your combo box and it will choose the right style.
<ComboBox ...>
<ComboBox.ItemContainerStyleSelector>
<local:CardsKeyStyleSelector/>
</ComboBox.ItemContainerStyleSelector>
</ComboBox>

My solution is further. First, define enumerable type and dictionary, associated with it:
Public Enum MyEnum As Integer
OneValue = 0
OtherValue = 1
End Enum
Public ReadOnly Property MyDict As Dictionary(Of MyEnum, String)
Get
Return New Dictionary(Of MyEnum, String) From {
{MyEnum.OneValue, "First value text"},
{MyEnum.OtherValue, "Other value text"}
}
End Get
End Property
Next, in XAML use it in this manner:
<Style TargetType="Ellipse" x:Key="ellipseStyle">
<Setter Property="Height" Value="10" />
<Setter Property="Width" Value="10" />
<Style.Triggers>
<DataTrigger Binding="{Binding Key}" Value="0"><!-- "0" - one of the dictionary key -->
<Setter Property="Fill" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Key}" Value="1"><!-- "1" - one of the dictionary key -->
<Setter Property="Fill" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ComboBox}" x:Key="cmb_osn_rez">
<Setter Property="ItemsSource" Value="{Binding MyDict}" />
<Setter Property="SelectedValuePath" Value="Key" />
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Style="{StaticResource ellipseStyle}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

Related

WPF - DataGrid column binding to attached property not working on context menu

I have ToggleButtons and a DataGrid, each row in DataGridColumn has a ColGroup AttachedProperty set to the name of the column group name.
Attached property:
public class DataGridColumnsGroupProperty {
public static readonly DependencyProperty ColGroupProperty =
DependencyProperty.RegisterAttached("ColGroup", typeof(object), typeof(DataGridColumnsGroupProperty), new FrameworkPropertyMetadata(null));
public static void SetColGroup(DependencyObject element, string value) {
element.SetValue(ColGroupProperty, value);
}
public static string GetColGroup(DependencyObject element) {
return (string)element.GetValue(ColGroupProperty);
}
}
The ToggleButtons has two jobs, on Check/UnCheck show/collapse all columns with the same group name.
and it has a ContextMenu which shows only the DataGridColumns with the same group name.
I've managed to bind all DataGridColumns to the ToggleButton, but couldn't find a way to Collapse the DataGridColumns with different group names.
How to fill context menu with only the columns with the givin group name inside the Style Trigger?
And how to hid all columns that has the group name when un-check toggle button?
XAML:
<ToggleButton.ContextMenu>
<ContextMenu x:Name="ContextMenu" ItemsSource="{Binding Columns, ElementName=ElementDataGrid, Converter={StaticResource TestConverter}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{Binding HeaderTemplate}"/>
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="StaysOpenOnClick" Value="True" />
<Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisibilityToBooleanConverter}}" />
<Style.Triggers>
<Trigger Property="attachedProperties:DataGridColumnsGroupProperty.ColGroup" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</ToggleButton.ContextMenu>
DataGridColumns:
<DataGridTextColumn x:Name="StoryCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="FirstGroup" Header="{x:Static p:Resources.Story}" IsReadOnly="True" Binding="{Binding Story}" Visibility="Visible" />
<DataGridTextColumn x:Name="CadIdCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="SecondGroup" Header="{x:Static p:Resources.CadId}" IsReadOnly="False" Binding="{Binding CadId}" Visibility="Visible" />
Using a DataTrigger should work as far as the binding to the attached property is concerned:
<DataTrigger Binding="{Binding Path=(attachedProperties:DataGridColumnsGroupProperty.ColGroup)}" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>

Set 2 type controls(TextBox and ComboBox ) in same DataGridTemplateColumn in XAML

I have a DataGrid.
Inside it contains some columns; 2 are related to this question, one is a DataGridTextColumn(x:Name="varTypeColumn") which show Variable type, another is a DataGridTemplateColumn(x:Name="varValueColumn") which could be a TextBox or ComboBox inside.
If varTypeColumn is Bool type, varValueColumn should show a ComboBox which contains 2 items: True, False. If varTypeColumn is Int Type, varValueColumn should show a TextBox which let user input string.
So my question is that, is it possible to do it in xaml? I find some implementation which do it in .cs code, it try to get Row and get the Cell, at last set TextBox/ComboBox instance to Cell's Content property. It can work, but if the DataGrid contains big number items(e.g., more than 5000), it is very very slow to display it.
below is the code part:
private void InitEditors()
{
for (int i = 0; i < _devLinkCollectionView.Count; i++)
{
DataGridRow row = devLinkDataGrid.GetRow(i);
InitEditor(row);
}
}
private void InitEditor(DataGridRow row)
{
DevLink link = row.Item as DevLink;
if (link != null)
{
if (link.HasErrors)
{
ToolTipService.SetShowOnDisabled(row, true);
row.IsEnabled = false;
return;
}
// Create binding first
Binding binding = new Binding("DefaultValue")
{
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
Source = link
};
DataGridCell cell = devLinkDataGrid.GetCell(row, 4);
switch (link.VariableType)
{
case CarelStandardDataType.Bool:
ComboBox comboBox = new ComboBox();
comboBox.ItemsSource = new[] {string.Empty, "TRUE", "FALSE" };
comboBox.SetBinding(ComboBox.SelectedValueProperty, binding);
cell.Content = comboBox;
break;
default:
TextBox textBox = new TextBox();
textBox.Style = (Style)FindResource("TextBoxInError");
cell.Content = textBox;
binding.ValidationRules.Add(new DevLinkValidationRule(link));
textBox.SetBinding(TextBox.TextProperty, binding);
break;
}
}
}
maybe this is what you want.
<Style x:Key="DataGridCellStyle1" TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Height="21.96">
<ComboBox x:Name="cbCondition1">
<ComboBoxItem Content="1"/>
<ComboBoxItem Content="2"/>
</ComboBox>
<TextBox x:Name="tbCondition2" Text="text"/>
</Grid>
<ControlTemplate.Triggers>
<!--In your case, you should use custom Converter, just return type, or maybe you'd better add typeProperty in your model-->
<DataTrigger Binding="{Binding TypeColumn}" Value="Int">
<Setter Value="Visible" TargetName="cbCondition1" Property="Visibility"/>
<Setter Value="Hidden" TargetName="tbCondition2" Property="Visibility"/>
</DataTrigger>
<DataTrigger Binding="{Binding TypeColumn}" Value="Bool">
<Setter Value="Hidden" TargetName="cbCondition1" Property="Visibility"/>
<Setter Value="Visible" TargetName="tbCondition2" Property="Visibility"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static DataGrid.FocusBorderBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource SampleDataSource}}">
<DataGrid Margin="160,106.5,119,139" ItemsSource="{Binding Collection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding TypeColumn}"/>
<DataGridTemplateColumn CellStyle="{StaticResource DataGridCellStyle1}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
==================sample data===============================
<SampleData:SampleDataSource xmlns:SampleData="clr-namespace:Expression.Blend.SampleData.SampleDataSource">
<SampleData:SampleDataSource.Collection>
<SampleData:Item TypeColumn="Int" ValueColumn="Row1"/>
<SampleData:Item TypeColumn="Bool" ValueColumn="Row2"/>
</SampleData:SampleDataSource.Collection>
</SampleData:SampleDataSource>

INotifyPropertyChange ignored in binding to a DataTrigger

I'm working on some code that has a Button that contains an image and some text, and which should display either the image, the text, or both, depending upon the value of a bound property. The code is currently using Styles and DataTriggers:
public enum ButtonStyle { Image, Text, Both };
public class ViewModel : INotifyPropertyChanged
{
private ButtonStyle _buttonStyle;
public ButtonStyle buttonStyle
{
get { return this._buttonStyle; }
set
{
this._buttonStyle = value;
notifyPropertyChanged("buttonStyle");
}
}
And:
<UserControl.Resources>
<Style x:Key="buttonTextStyle" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=buttonStyle}"
Value="{x:Static local:ButtonStyle.Text}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="buttonImageStyle" TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=buttonStyle}"
Value="{x:Static local:ButtonStyle.Image}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Button>
<StackPanel>
<Image Source="..." Style="{StaticResource buttonImageStyle} />
<Label Style={StaticResource buttonTextStyle}>My Text</Label>
</StackPanel>
</Button>
My problem? The button doesn't change when I change the value of the buttonStyle property in the view model. This control is in a tab, and if I switch to another tab and then switch back, the button updates to reflect the current value of the buttonStyle property, but it does not change until I do.
It looks like the DataTrigger is processed only when the control is rendered, and does not re-render when the bound value is modified, despite the bound value raising a PropertyChanged event.
Any ideas?
Try NotifyOnSourceUpdated=True on each of your data triggers.
<DataTrigger Binding="{Binding Path=buttonStyle, NotifyOnSourceUpdated=True}"
Value="{x:Static local:ButtonStyle.Text}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
I think this is a nicer way to refer to enums in your DataTrigger:
<Style x:Key="buttonImageStyle" TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=buttonStyle}">
<DataTrigger.Value>
<local:ButtonStyle>Text</local:ButtonStyle>
</DataTrigger.Value>
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
The value of the resource changes during runtime, thats why you should use DynamicResource instead of StaticResource:
Style="{DynamicResource buttonImageStyle}"
Here's an idea - any time you have a binding problem and it looks like INotifyPropertyChanged isn't working, check and double check and make damned sure that you spelled the name of the property right, in your PropertyChangedEventArgs().
Sorry for the trouble.

Change ListView.ItemTemplate on subelement change

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

Using custom dependency properties as DataTrigger in WPF

I have a custom dependency property that I would like to use as a data trigger. Here is the code behind:
public static readonly DependencyProperty BioinsulatorScannedProperty =
DependencyProperty.Register(
"BioinsulatorScanned",
typeof(bool),
typeof(DisposablesDisplay),
new FrameworkPropertyMetadata(false));
public bool BioinsulatorScanned
{
get
{
return (bool)GetValue(BioinsulatorScannedProperty);
}
set
{
SetValue(BioinsulatorScannedProperty, value);
}
}
I have created a style and control template. My goal is to change the color of some text when the dependency prop is set to true...
<Style x:Key="TreatEye" TargetType="Label">
<Setter Property="Foreground" Value="#d1d1d1" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Canvas>
<TextBlock x:Name="bioinsulatorText"
Canvas.Left="21" Canvas.Top="33"
Text="Bioinsulator" />
<TextBlock Canvas.Left="21" Canvas.Top="70"
Text="KXL Kit" />
</Canvas>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding BioinsulatorScanned}"
Value="True">
<Setter TargetName="bioinsulatorText"
Property="Foreground" Value="Black" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Despite successfully setting the dependency prop to true programmatically, This trigger condition never fires. This is a real pain to debug!
Thanks in advance.
In this case I am switching the visibility of a button using a datatrigger based on a dependency property FirstLevelProperty.
public static readonly DependencyProperty FirstLevelProperty = DependencyProperty.Register("FirstLevel", typeof(string), typeof(MyWindowClass));
public string FirstLevel
{
get
{
return this.GetValue(FirstLevelProperty).ToString();
}
set
{
this.SetValue(FirstLevelProperty, value);
}
}
You can reference the dependency property FirstLevel(Property) contained (in this case) in a window by using the RelativeSource binding. Also you should set the default setting in the style, that will be overridden by the datatrigger.
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=FirstLevel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"
Value="SomeValue">
<Setter Property="Visibility"
Value="Hidden" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Visible" />
</Style>
</Button.Style>
It looks like your dependency property is defined inside a DisposableDisplay object that you created. In order for the binding specified to work, an instance of that DisposableDisplay object must be set as the DataContext of the control (label in this case) or any of its ancestors.

Resources