WPF data to bind style - wpf

here is my style .xaml:
<Style TargetType="TabItem" x:Key="gMetroTabItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="6,2,6,2" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="MinWidth" Value="5" />
<Setter Property="MinHeight" Value="5" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type local:TabItemHeaderData}">
<StackPanel>
<TextBlock x:Name="rootText" Text="{Binding tabText}" FontSize="26.67"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
here is my TabItemHeaderData:
public class TabItemHeaderData
{
public String tabText { get; set; }
public object Content { get; set; }
public object tabIconOff { get; set; }
public object tabIconOn { get; set; }
}
my question is: the code of the style seems right, I bind the property tabText to the Text, but how can I send the TabItemHeaderData from my window code? actually is like this:
<TabItem Style="{StaticResource gMetroTabItem}">
<Grid>
...
</Grid>
</TabItem>

Related

Dynamic resource merging not applying styles to first control

I tried to apply new style for my custom controls by merging the style resources to the application resources while adding the control to UI, but new styles are not applied to the controls for the first time.
Sample control
CustomTextBoxExt.cs
public class CustomTextBoxExt : TextBox
{
static CustomTextBoxExt()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomTextBoxExt), new FrameworkPropertyMetadata(typeof(CustomTextBoxExt)));
}
}
default style
Generic.xaml
<Style x:Key="TextBoxExtStyle" TargetType="{x:Type local:CustomTextBoxExt}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="AllowDrop" Value="True" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBoxExt}">
<Border
x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ScrollViewer
x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Opacity" Value="0.56" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#FF7EB4EA" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#FF569DE5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="True" />
<Condition Property="IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}" />
</MultiTrigger>
</Style.Triggers>
</Style>
<Style BasedOn="{StaticResource TextBoxExtStyle}" TargetType="{x:Type local:CustomTextBoxExt}" />
Custom theme
TextBoxExtStyle.xaml
<Style x:Key="MaterialTextBoxExtStyle" TargetType="{x:Type local:CustomTextBoxExt}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Foreground" Value="#DD000000" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="#FF9E9E9E" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="SelectionBrush" Value="#FF0279FF" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="CaretBrush" Value="#DD000000" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBoxExt}">
<Grid>
<Border
x:Name="border"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<ScrollViewer
x:Name="PART_ContentHost"
Background="Transparent"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="#FF757575" />
<Setter TargetName="border" Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#DD000000" />
<Setter Property="CaretBrush" Value="#DD000000" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="#FF0279FF" />
<Setter Property="BorderThickness" Value="0,0,0,2" />
<Setter Property="Padding" Value="0,0,0,-1" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="Transparent" />
<Setter TargetName="border" Property="BorderBrush" Value="#FFE0E0E0" />
<Setter Property="Foreground" Value="#60000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource MaterialTextBoxExtStyle}" TargetType="{x:Type local:CustomTextBoxExt}" />
and using attached property, trying to change style from default style,
public class SkinExt
{
public static string GetTheme(DependencyObject obj)
{
return (string)obj.GetValue(ThemeProperty);
}
public static void SetTheme(DependencyObject obj, string value)
{
obj.SetValue(ThemeProperty, value);
}
// Using a DependencyProperty as the backing store for Theme. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ThemeProperty =
DependencyProperty.RegisterAttached("Theme", typeof(string), typeof(SkinExt), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnVisualStyleChanged)));
private static void OnVisualStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue && !string.IsNullOrEmpty(e.NewValue.ToString()) /*&& d is FrameworkElement && (d as FrameworkElement).IsLoaded*/)
{
SkinExt.ApplyTheme(d, e.NewValue.ToString());
}
}
internal static void ApplyTheme(DependencyObject obj, string style)
{
Type itemType = obj.GetType();
List<string> styles = GetDictionaries(obj.GetType().Name.ToString(), style);
if (styles != null && styles.Count > 0)
{
foreach (var path in styles)
{
var rdict = new ResourceDictionary() { Source = new Uri(path, UriKind.RelativeOrAbsolute) };
bool alreadyExist = false;
foreach (var dictionaryFiles in Application.Current.Resources.MergedDictionaries)
{
if (dictionaryFiles.Source.OriginalString.Contains(path))
{
alreadyExist = true;
break;
}
}
if (!alreadyExist)
{
Application.Current.Resources.MergedDictionaries.Add(rdict);
Console.WriteLine(path);
}
}
}
}
internal static List<string> GetDictionaries(String type, string style)
{
List<string> styles = new List<string>();
#region Switch
switch (type)
{
case "CustomTextBoxExt":
styles.Add("/TextBoxExt;component/TextBoxExt/TextBoxExtStyle.xaml");
break;
case "ButtonExt":
styles.Add("/TextBoxExt;component/ButtonExt/ButtonExtStyle.xaml");
break;
case "Label":
styles.Add("/TextBoxExt;component/LabelStyle.xaml");
break;
}
# endregion
return styles;
}
}
setting
local:SkinExt.Theme="Material"
in mainwindow/grid works as expected when children are added directly. But, when using below lazyextension style is not working.
public static class LazyLoadExtensions
{
public static LazyUIElementCollection GetLazyChildrens(DependencyObject obj)
{
return (LazyUIElementCollection)obj.GetValue(LazyChildrensProperty);
}
public static void SetLazyChildrens(DependencyObject obj, LazyUIElementCollection value)
{
obj.SetValue(LazyChildrensProperty, value);
}
public static readonly DependencyProperty LazyChildrensProperty =
DependencyProperty.RegisterAttached("LazyChildrens", typeof(LazyUIElementCollection), typeof(LazyLoadExtensions), new PropertyMetadata(OnLazyChildrensChanged));
private static void OnLazyChildrensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wrapPanel = d as WrapPanel;
var childrens = LazyLoadExtensions.GetLazyChildrens(wrapPanel);
for (int i = 0; i < childrens.Count; i++)
{
var child = childrens[i];
wrapPanel.Children.Add(child);
}
}
}
public class LazyUIElementCollection : List<UIElement>
{
public LazyUIElementCollection()
{
}
}
Works
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<!--<local:LazyLoadExtensions.LazyChildrens>-->
<!--<local:LazyUIElementCollection>-->
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
<!--</local:LazyUIElementCollection>-->
<!--</local:LazyLoadExtensions.LazyChildrens>-->
</WrapPanel>
</Grid>
Not Working
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<local:LazyLoadExtensions.LazyChildrens>
<local:LazyUIElementCollection>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
</local:LazyUIElementCollection>
</local:LazyLoadExtensions.LazyChildrens>
</WrapPanel>
</Grid>
Works for second item
Style applying correctly for second customtextboxext
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<local:LazyLoadExtensions.LazyChildrens>
<local:LazyUIElementCollection>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
</local:LazyUIElementCollection>
</local:LazyLoadExtensions.LazyChildrens>
</WrapPanel>
</Grid>
Reproducible sample : https://drive.google.com/open?id=1iB9sY90T7aRaaRTzVc1EvE2qFU13fHG7
Check the above sample and let me know your ideas
cool project !
I'm not exactly sure why the problem occurs but it seems like a timing problem.
I found the following workaround:
In LazyLoadExtensions:
private static void OnLazyChildrensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wrapPanel = d as WrapPanel;
var childrens = LazyLoadExtensions.GetLazyChildrens(wrapPanel);
for (int i = 0; i < childrens.Count; i++)
{
var child = childrens[i];
//The dictionaries containing the styles are added
SkinExt.SetTheme(child, SkinExt.GetTheme(wrapPanel));
wrapPanel.Children.Add(child);
}
}
This way the dictionaries containing the style are merged before the the children are added and the styles are correctly applied.
Edit:
Since you can't change the LazyLoadExtensions class:
In SkinExt class:
internal static void ApplyTheme(DependencyObject obj, string style)
{
Type itemType = obj.GetType();
List<string> styles = GetDictionaries(obj.GetType().Name.ToString(), style);
if (styles != null && styles.Count > 0)
{
foreach (var path in styles)
{
var rdict = new ResourceDictionary() { Source = new Uri(path, UriKind.RelativeOrAbsolute) };
bool alreadyExist = false;
foreach (var dictionaryFiles in Application.Current.Resources.MergedDictionaries)
{
if (dictionaryFiles.Source.OriginalString.Contains(path))
{
alreadyExist = true;
break;
}
}
if (!alreadyExist)
{
Application.Current.Resources.MergedDictionaries.Add(rdict);
if (obj is FrameworkElement frameworkElement && frameworkElement.IsInitialized)
{
//The style won't be applied automaticaly
frameworkElement.Style = rdict.Values.OfType<Style>().First(s => s.TargetType == itemType);
}
else
{
//Nothing to do, style will be applied automaticaly
}
Console.WriteLine(path);
}
}
}
}
If the IsInitialized property is true, the style from the added ressourceDictionnary won't be applied, you can check it in debugger.
https://learn.microsoft.com/en-gb/dotnet/api/system.windows.frameworkelement.isinitialized?view=netcore-3.1#System_Windows_FrameworkElement_IsInitialized
If it's helpful, applying theme inside dispatcher works as expected ,
d.Dispatcher.BeginInvoke(new Action(() =>
{
SkinExt.ApplyTheme(d, e.NewValue.ToString());
}));

WPF MVVM changing property under controltemplate programmatically

I want to change Border Background programmatically depends on WeekType property. It has All, Even and Odd property. Color of my items are in Border's Background property. So when property is All I want them to stay LightGray, but when All or Even I want to change color of this border
XAML:
<Style TargetType="{x:Type summary:SummaryHourUnitItem}">
<Setter Property="StartTime" Value="{Binding StartTime}" />
<Setter Property="EndTime" Value="{Binding EndTime}" />
<Setter Property="WeekType" Value="{Binding WeekType}" />
<Setter Property="SubGroup" Value="{Binding SubGroup}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type summary:SummaryHourUnitItem}">
<Border Width="Auto" BorderThickness="1,1,1,1" BorderBrush="Black" Background="LightGray"
Margin="0" Padding="3,1.5,0,1.5" >
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="9" />
<Setter Property="Foreground" Value="Black" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SummaryHourUnitItem class:
public class SummaryHourUnitItem : ButtonBase
{
public static readonly DependencyProperty StartTimeProperty =
SummaryTimeSlotPanel.StartTimeProperty.AddOwner(typeof(SummaryHourUnitItem));
public static readonly DependencyProperty EndTimeProperty =
SummaryTimeSlotPanel.EndTimeProperty.AddOwner(typeof(SummaryHourUnitItem));
public static readonly DependencyProperty WeekTypeProperty =
SummaryTimeSlotPanel.WeekTypeProperty.AddOwner(typeof(SummaryHourUnitItem));
public static readonly DependencyProperty SubGroupProperty =
SummaryTimeSlotPanel.SubGroupProperty.AddOwner(typeof(SummaryHourUnitItem));
static SummaryHourUnitItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SummaryHourUnitItem),
new FrameworkPropertyMetadata(typeof(SummaryHourUnitItem)));
}
public bool StartTime
{
get => (bool) GetValue(StartTimeProperty);
set => SetValue(StartTimeProperty, value);
}
public bool EndTime
{
get => (bool) GetValue(EndTimeProperty);
set => SetValue(EndTimeProperty, value);
}
public WeekType WeekType
{
get => (WeekType) GetValue(WeekTypeProperty);
set
{
SetValue(WeekTypeProperty, value);
}
}
public SubGroup SubGroup
{
get => (SubGroup)GetValue(SubGroupProperty);
set => SetValue(SubGroupProperty, value);
}
}
How can I achieve that?
You must to use a Style Trigger.
<Border Width="Auto" BorderThickness="1,1,1,1" BorderBrush="Black" Background="LightGray"
Margin="0" Padding="3,1.5,0,1.5" >
<Border.Style>
<Style BasedOn="{StaticResource {x:Type Border}}" TargetType="{x:Type Border}">
<Setter Property="Background" Value="LightGray" />
<Style.Triggers>
<DataTrigger Binding="{Binding WeekType}" Value="XXXX">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="9" />
<Setter Property="Foreground" Value="Black" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
I hope it works for you.

Adding an icon to a cell in a WPF DecExpress grid

I need a fresh set of eyes because I am doing something wrong and can't figure out what. I am trying to add an icon to a DevExpress grid. The icon will be clickable to open the survey for editing. The icon is not displaying.
In the SurveyVMO:
public Image EditIcon { get; set; }
public static List<VMOSurvey> PopulateSurveyList()
{
List<Survey> surveyList = Survey.PopulateSurveyList();
List<VMOSurvey> surveys = new List<VMOSurvey>();
foreach (Survey svy in surveyList)
{
VMOSurvey s = new VMOSurvey(svy);
if (svy.IsEditable)
s.EditIcon = Image.FromFile(#".\Images\pencil.png");
surveys.Add(s);
}
return surveys;
}
Then in the ManageSurveyWindow.xaml:
<Window.Resources>
<Style x:Key="SurveyGrid" TargetType="{x:Type dxg:GridControl}">
<Setter Property="AllowLiveDataShaping" Value="True"/>
<Setter Property="SelectionMode" Value="Row"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Thin"/>
<Setter Property="FontFamily" Value="Helvetica"/>
<Setter Property="Margin" Value="10, 10, 10, 10"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
<Style x:Key="ColumnHeaderStyling" TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Helvetica"/>
<Setter Property="FontWeight" Value="Thin"/>
<Setter Property="FontSize" Value="24"/>
</Style>
<Style x:Key="ManageSurveyTableStyling" TargetType="{x:Type dxg:TableView}">
<Setter Property="ShowFixedTotalSummary" Value="True"/>
<Setter Property="IsTotalSummaryMenuEnabled" Value="False"/>
<Setter Property="ShowGroupedColumns" Value="False" />
<Setter Property="ShowGroupPanel" Value="False"/>
<Setter Property="EnableImmediatePosting" Value="True"/>
<Setter Property="AutoWidth" Value="True"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalScrollbarVisibility" Value="Auto"/>
</Style>
</Window.Resources>
<Grid>
<dxg:GridControl AllowLiveDataShaping="True" ItemsSource="{Binding Surveys}" >
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="EditIconColumn">
<dxg:GridColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text=" " Style="{StaticResource ColumnHeaderStyling}"/>
</DataTemplate>
</dxg:GridColumn.HeaderTemplate>
<dxg:GridColumn.HeaderStyle>
<Style TargetType="Control">
<Setter Property="Background" Value="#52658F"/>
</Style>
</dxg:GridColumn.HeaderStyle>
<DataTemplate>
<Image Height="32" Width="32" Source="{Binding EditIcon}" />
</DataTemplate>
</dxg:GridColumn>
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:TableView AllowEditing="False" Style="{StaticResource ManageSurveyTableStyling}"/>
</dxg:GridControl.View>
</dxg:GridControl>
</Grid>
I did figure it out. My EditIcon needed to be an ImageSource:
public ImageSource EditIcon { get; set; }
And then I had to add the following code to my xaml file:
<dxg:GridColumn.EditSettings >
<dxe:ImageEditSettings MaxWidth="30">
</dxe:ImageEditSettings>
Now I'm trying to figure out how to add a click event to the image. :)

WPF DataGrid Dynamic width cell highlighting

I have an application that's using the WPF Data Grid. That grid presents a set of test results. If the result of a test is out side the min and max allowed values I want to highlight that cell in red. I currently have it working, but am not quite happy with the highlighting.
Here's what it looks like currently:
Here's the desired look (via some image twiddling):
Notice the highlighting in the first example consumes the entire cell width. I'm hoping for the desired example where it only consumes as much space as the widest result with a little margin on both sides. Keep in mind, a result in any one cell could range between 0 and 1920K from one sample to the next. This is an edge case, but I want the highlighted area to grow and shrink as a result.
Just FYI, these results are updated on a configurable timer that triggers anywhere between 10 ms and 10 seconds depending on the user configuration.
Below is the code that generates the first example (sorry for the large amount of code). The interesting bits are DataGridCellStyle, ResultCellStyle and CellTemplate
The XAML
<Window x:Class="StackOverflow_HighlightCell.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StackOverflow_HighlightCell"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="White" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<ControlTemplate x:Key="CellTemplate" TargetType="{x:Type DataGridCell}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Margin="12,0,0,0" />
</Border>
</ControlTemplate>
<Style x:Key="DataGridCellStyle" TargetType="DataGridCell">
<Setter Property="Background" Value="#707070" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#CCCCCC" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="ResultCellStyle" TargetType="DataGridCell"
BasedOn="{StaticResource DataGridCellStyle}">
<Setter Property="Template" Value="{StaticResource CellTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsResultOutOfBounds,
StringFormat={}{0:0.00}}"
Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGrid" BasedOn="{x:Null}">
<Setter Property="RowBackground" Value="#707070" />
<Setter Property="AutoGenerateColumns" Value="False" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Background" Value="#666666" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CanUserSortColumns" Value="False" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Foreground" Value="#CCCCCC" />
<Setter Property="RowDetailsVisibilityMode" Value="Collapsed" />
<Setter Property="CellStyle" Value="{StaticResource DataGridCellStyle}" />
</Style>
<!-- A left justified DataGridTextColumn -->
<Style x:Key="ElementLeftJustified">
<Setter Property="TextBlock.HorizontalAlignment" Value="Left" />
<Setter Property="TextBlock.Margin" Value="15,0,5,0" />
</Style>
<!-- A right justified DataGridTextColumn -->
<Style x:Key="ElementRightJustified">
<Setter Property="TextBlock.HorizontalAlignment" Value="Right" />
<Setter Property="TextBlock.Margin" Value="0,0,5,0" />
</Style>
</Window.Resources>
<Grid Background="#FF666666">
<Border Margin="20" >
<DataGrid x:Name="_testSummaryGrid"
ItemsSource="{Binding TestResults}">
<DataGrid.Columns>
<DataGridTextColumn
MinWidth="75" Header="Test"
Binding="{Binding TestName}"
ElementStyle="{StaticResource ElementLeftJustified}" />
<DataGridTextColumn
MinWidth="75" Header="Min"
Binding="{Binding Min, StringFormat={}{0:0.00}}"
ElementStyle="{StaticResource ElementRightJustified}" />
<DataGridTextColumn
MinWidth="75" Header="Result"
Binding="{Binding Result, StringFormat={}{0:0.00}}"
ElementStyle="{StaticResource ElementRightJustified}"
CellStyle="{StaticResource ResultCellStyle}" />
<DataGridTextColumn
MinWidth="75" Header="Max"
Binding="{Binding Max, StringFormat={}{0:0.00}}"
ElementStyle="{StaticResource ElementRightJustified}" />
</DataGrid.Columns>
</DataGrid>
</Border>
</Grid>
</Window>
The View Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace StackOverflow_HighlightCell
{
public class ResultsViewModel : ViewModelBase
{
public ResultsViewModel()
{
TestResults = new ObservableCollection<TestResult>
{
{ new TestResult { TestGroup = "Circle",TestName = "Radius",
Min = 100, Max = 153, Result = 150} },
{ new TestResult { TestGroup = "Circle", TestName = "Min Radius",
Min = 0, Max = 90, Result = 97.59 } },
// And so on ...
};
}
public ObservableCollection<TestResult> TestResults { get; set; }
}
public class TestResult : ViewModelBase
{
public string TestGroup { get; set; }
public string TestName { get; set; }
public double Result { get; set; }
public double Min { get; set; }
public double Max { get; set; }
public bool IsResultOutOfBounds { get { return !(Result >= Min && Result <= Max); } }
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
I think this should do what you want:
<ControlTemplate x:Key="ResultCellTemplate" TargetType="{x:Type DataGridCell}">
<Border Background="{TemplateBinding Background}">
<Grid
Margin="12,0,0,0"
HorizontalAlignment="Right"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Result" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" x:Name="ContentPresenterBorder">
<ContentPresenter
/>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!--
That stringformat you had will have been ignored because the target
type isn't string.
-->
<DataTrigger Binding="{Binding IsResultOutOfBounds}" Value="True">
<Setter TargetName="ContentPresenterBorder" Property="Background" Value="Red" />
<Setter TargetName="ContentPresenterBorder" Property="TextElement.Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="ResultCellStyle" TargetType="DataGridCell"
BasedOn="{StaticResource DataGridCellStyle}">
<Setter Property="Template" Value="{StaticResource ResultCellTemplate}" />
</Style>
...but don't forget to set Grid.IsSharedSizeScope="True" on the DataGrid. That works with SharedSizeGroup="Result" on the ColumnDefinition to ensure that any grid column named "Result" anywhere within the DataGrid will be dynamically sized to the same width.
<DataGrid
x:Name="_testSummaryGrid"
ItemsSource="{Binding TestResults}"
Grid.IsSharedSizeScope="True"
>
Excellent example by the way. Would've been better trimmed down to just two DataGrid columns, but I pasted it in, pressed F5, and it worked. And the important part wasn't hard to find.

WPF ComboboxItems with button

I have created my own combobox style along with stylign combobox items. The dropdown needs to display some items with buttons and others without my items are created dynamically as follows:
ComboBoxItem typeItem = new ComboBoxItem();
typeItem.Content = "Test";
typeItem.Tag = i;
MyCombobox.Items.Add(typeItem);
My question is how do I apply this style to some items and not others, my two comboboxitems are bellow?
<Style x:Key="{x:Type ComboBoxItem}" TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="MinHeight" Value="{DynamicResource ResourceKey=MinimumIteractSizeDips}" />
<Setter Property="MinWidth" Value="{DynamicResource ResourceKey=MinimumIteractSizeDips}" />
<Setter Property="Foreground" Value="Black"/>
</Style>
<Style x:Key="MyComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="MinHeight" Value="{DynamicResource ResourceKey=MinimumIteractSizeDips}" />
<Setter Property="MinWidth" Value="{DynamicResource ResourceKey=MinimumIteractSizeDips}" />
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Grid>
<Label Content="{TemplateBinding Content}" Foreground="Black" Background="White" Height="26" HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0"/>
<Button Grid.Column="2" Grid.Row="0" Content="Settings" Height="23" HorizontalAlignment="Left" Margin="2" Name="PositionSettings" VerticalAlignment="Center" Width="57"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Check the StyleSelector class.
public class MyStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var myVm = item as MyViewModel;
if(myVm.EditMode)
return element.FindResource("TemplateWithButtons") as DataTemplate
return element.FindResource("TemplateNormal") as DataTemplate
}
}
and in xaml, define a resource for your selector
<MyStyleSelector x:Key="myStyleSelector"/>
and apply that to your combobox.
<ComboBox StyleSelector="{StaticResource myStyleSelector}"/>
btw. x:Key with the same type as TargetType is redundant.
Final Note: You should not override the style for that. What you want is DataTemplate and the DataTemplate selector. Also you should not bind the Content(type object) to a labels text (type string), a ContentPresenter is the way to go here.

Resources