WPF TreeView with checkboxes - wpf

After a lot of searching, I have not found any solution for the following problem.
I need a treeview control with "checkboxed" treeview items and the CheckedItems property for convenient data binding (for example, treeview of folders' structure, when user checks folders, the size of checked folders is displayed in a textbox).
By the way, I have read the article «Working with Checkboxes in the WPF TreeView», Josh Smith, but the "IsChecked" approach is not appropriate in my case because I need to bind CheckedItems as a collection.
I would appreciate any help!
The image link has been attached. I want the listbox to be data bound to CheckedItems property of CheckTreeView. Does anybody know how to implement the generic CheckTreeView with possible binding to CheckedItems collection?

Update
Finally got around to update the CheckBoxTreeView with the missing features. The CheckBoxTreeViewLibrary source can be downloaded here
Adds the CheckedItems property
CheckedItems is an ObservableCollection<T> where T is the interal type of ItemsSource
CheckedItems supports two-way Binding to source
If a CheckBoxTreeViewItem hasn't been generated yet (not expanded to) then the source for it won't be in the CheckedItems collection until it has been generated
The Control can be used just like a regular TreeView. To add two-way binding for the IsChecked property, the CheckBoxTreeViewItemStyle.xaml ResourceDictionary must be merged. e.g.
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/CheckBoxTreeViewLibrary;component/Themes/CheckBoxTreeViewItemStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Then the ItemContainerStyle can be used like this
<cbt:CheckBoxTreeView ...>
<cbt:CheckBoxTreeView.ItemContainerStyle>
<Style TargetType="{x:Type cbt:CheckBoxTreeViewItem}"
BasedOn="{StaticResource {x:Type cbt:CheckBoxTreeViewItem}}">
<Setter Property="IsChecked" Value="{Binding IsChecked}"/>
<!-- additional Setters, Triggers etc. -->
</Style>
</cbt:CheckBoxTreeView.ItemContainerStyle>
</cbt:CheckBoxTreeView>
CheckBoxTreeView.cs
namespace CheckBoxTreeViewLibrary
{
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CheckBoxTreeViewItem))]
public class CheckBoxTreeView : TreeView
{
public static DependencyProperty CheckedItemsProperty =
DependencyProperty.Register("CheckedItems",
typeof(IList),
typeof(CheckBoxTreeView));
private RoutedEventHandler Checked_EventHandler;
private RoutedEventHandler Unchecked_EventHandler;
public CheckBoxTreeView()
: base()
{
Checked_EventHandler = new RoutedEventHandler(checkBoxTreeViewItem_Checked);
Unchecked_EventHandler = new RoutedEventHandler(checkBoxTreeViewItem_Unchecked);
DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(CheckBoxTreeView.ItemsSourceProperty, typeof(CheckBoxTreeView));
if (dpd != null)
{
dpd.AddValueChanged(this, ItemsSourceChanged);
}
}
void ItemsSourceChanged(object sender, EventArgs e)
{
Type type = ItemsSource.GetType();
if (ItemsSource is IList)
{
Type listType = typeof(ObservableCollection<>).MakeGenericType(type.GetGenericArguments()[0]);
CheckedItems = (IList)Activator.CreateInstance(listType);
}
}
internal void OnNewContainer(CheckBoxTreeViewItem newContainer)
{
newContainer.Checked -= Checked_EventHandler;
newContainer.Unchecked -= Unchecked_EventHandler;
newContainer.Checked += Checked_EventHandler;
newContainer.Unchecked += Unchecked_EventHandler;
}
protected override DependencyObject GetContainerForItemOverride()
{
CheckBoxTreeViewItem checkBoxTreeViewItem = new CheckBoxTreeViewItem();
OnNewContainer(checkBoxTreeViewItem);
return checkBoxTreeViewItem;
}
void checkBoxTreeViewItem_Checked(object sender, RoutedEventArgs e)
{
CheckBoxTreeViewItem checkBoxTreeViewItem = sender as CheckBoxTreeViewItem;
Action action = () =>
{
var checkedItem = checkBoxTreeViewItem.Header;
CheckedItems.Add(checkedItem);
};
this.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
void checkBoxTreeViewItem_Unchecked(object sender, RoutedEventArgs e)
{
CheckBoxTreeViewItem checkBoxTreeViewItem = sender as CheckBoxTreeViewItem;
Action action = () =>
{
var uncheckedItem = checkBoxTreeViewItem.Header;
CheckedItems.Remove(uncheckedItem);
};
this.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
public IList CheckedItems
{
get { return (IList)base.GetValue(CheckedItemsProperty); }
set { base.SetValue(CheckedItemsProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
CheckBoxTreeViewItem.cs
namespace CheckBoxTreeViewLibrary
{
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CheckBoxTreeViewItem))]
public class CheckBoxTreeViewItem : TreeViewItem
{
public static readonly RoutedEvent CheckedEvent = EventManager.RegisterRoutedEvent("Checked",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(CheckBoxTreeViewItem));
public static readonly RoutedEvent UncheckedEvent = EventManager.RegisterRoutedEvent("Unchecked",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(CheckBoxTreeViewItem));
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked",
typeof(bool),
typeof(CheckBoxTreeViewItem),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
CheckedPropertyChanged));
private static void CheckedPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
CheckBoxTreeViewItem checkBoxTreeViewItem = (CheckBoxTreeViewItem)source;
if (checkBoxTreeViewItem.IsChecked == true)
{
checkBoxTreeViewItem.OnChecked(new RoutedEventArgs(CheckedEvent, checkBoxTreeViewItem));
}
else
{
checkBoxTreeViewItem.OnUnchecked(new RoutedEventArgs(UncheckedEvent, checkBoxTreeViewItem));
}
}
public CheckBoxTreeViewItem()
: base()
{
}
protected override DependencyObject GetContainerForItemOverride()
{
PropertyInfo parentTreeViewPi = typeof(TreeViewItem).GetProperty("ParentTreeView", BindingFlags.Instance | BindingFlags.NonPublic);
CheckBoxTreeView parentCheckBoxTreeView = parentTreeViewPi.GetValue(this, null) as CheckBoxTreeView;
CheckBoxTreeViewItem checkBoxTreeViewItem = new CheckBoxTreeViewItem();
parentCheckBoxTreeView.OnNewContainer(checkBoxTreeViewItem);
return checkBoxTreeViewItem;
}
[Category("Behavior")]
public event RoutedEventHandler Checked
{
add
{
AddHandler(CheckedEvent, value);
}
remove
{
RemoveHandler(CheckedEvent, value);
}
}
[Category("Behavior")]
public event RoutedEventHandler Unchecked
{
add
{
AddHandler(UncheckedEvent, value);
}
remove
{
RemoveHandler(UncheckedEvent, value);
}
}
public bool IsChecked
{
get { return (bool)base.GetValue(IsCheckedProperty); }
set { base.SetValue(IsCheckedProperty, value); }
}
protected virtual void OnChecked(RoutedEventArgs e)
{
base.RaiseEvent(e);
}
protected virtual void OnUnchecked(RoutedEventArgs e)
{
base.RaiseEvent(e);
}
}
}
CheckBoxTreeViewItemStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cti="clr-namespace:CheckBoxTreeViewLibrary">
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="Transparent" Stroke="#FF989898">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1BBBFA"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="Transparent"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type cti:CheckBoxTreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cti:CheckBoxTreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="15" Width="Auto"/>
<!--<ColumnDefinition Width="Auto"/>-->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="15"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,2,4,0" x:Name="PART_CheckedCheckBox" IsChecked="{Binding IsChecked, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>

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: Change Progress-Bar color according its value and View Model property

Ok so i have this property in my View Model:
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged(); // I also try OnPropertyChanged("IsChecked");
}
}
And as you can see this implement INotifyPropertyChanged.
When this property is changing i can see the it notify and get the correct value.
This is my Progress-Bar:
<ProgressBar Name="progressBarColumn"
Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding Path=Width, ElementName=ProgressCell}"
Style="{StaticResource CustomProgressBar}" />
And my Progress-Bar style:
<Style x:Key="CustomProgressBar" TargetType="ProgressBar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="{DynamicResource ProgressBackgroundColor}"
BorderBrush="{DynamicResource ProgressBackgroundColor}"
BorderThickness="1" />
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{DynamicResource ProgressBarFillColor}"
BorderBrush="{DynamicResource ProgressBarFillColor}"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource AncestorType=ProgressBar}}">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Value="False" Binding="{Binding IsChecked}">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter TargetName="Root" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
<Setter TargetName="Root" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}"/>
<Setter TargetName="Root" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So this is the things that currently not working:
When my Progress-Bar reach the value of 100% i want its Background color will change so i add this DataTrigger:
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource AncestorType=ProgressBar}}">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
So this not effect at all over my Pregress-Bar color when its reach 100%.
I even also try:
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource Self}}">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
When my View Model IsChecked property is set to false i want that my Progress-Bar Background color will change to Red - also this is not working.
I try to search many solutions but again and again same results - my Progress-Bar``color not changing at all.
EDIT:
<Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
<Style.Triggers>
<Trigger Value="100" Property="Value">
<Setter Property="Background" Value="{DynamicResource ProgressBarFillCompleteColor}"/>
<Setter Property="Foreground" Value="Transparent"/>
</Trigger>
<DataTrigger Value="False" Binding="{Binding IsChecked}">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="{DynamicResource ProgressBorderBrushColor}"
BorderBrush="{DynamicResource ProgressBorderBrushColor}"
BorderThickness="1" />
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Background}"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
First of all you need properly implement INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
private int _Progress;
private int _Progress;
public int Progress
{
get {
return _Progress;
}
set
{
_Progress = value;
PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
}
}
Then change your style. It will be better to use Style.Triggers instead
<Style.Triggers>
<Trigger Value="100" Property="Value">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="Pink"/>
</Trigger>
<DataTrigger Value="False" Binding="{Binding IsChecked}">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
And modify background binding on your barders inside template
<Border Name="PART_Track"
CornerRadius="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Background}"
BorderThickness="1" />
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{TemplateBinding Foreground}"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="1"
HorizontalAlignment="Left" />
It will be enough to change Background when progress will be 100, but when progress will be more than 100 ( if you need it) foreground and background will be switched to default value. If you need to change color for progress >= 100 then you need ValueConverter instead of triggers.
EDIT:
Full solution.
<Window x:Class="WpfApplication3.ProgressWindow"
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:WpfApplication3"
mc:Ignorable="d"
Title="ProgressWindow" Height="200" Width="400">
<Window.Resources>
<SolidColorBrush x:Key="ProgressBarFillCompleteColor" Color="Green"/>
<SolidColorBrush x:Key="ProgressBorderBrushColor" Color="Gray"/>
<Style x:Key="CustomProgressBar" TargetType="ProgressBar">
<Setter Property="Foreground" Value="AliceBlue"/>
<Setter Property="Background" Value="{DynamicResource ProgressBorderBrushColor}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Background}"
BorderThickness="1" />
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{TemplateBinding Foreground}"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter TargetName="Root" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
<Setter TargetName="Root" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}"/>
<Setter TargetName="Root" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Value="False" Binding="{Binding IsChecked}">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<Trigger Value="100" Property="Value">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel>
<ProgressBar Name="progressBarColumn" DockPanel.Dock="Top" Height="20" Minimum="0" Maximum="100"
Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource CustomProgressBar}" />
<StackPanel>
<Button Content="Start" Click="Button_Click"/>
<CheckBox Content="Check me" IsChecked="{Binding IsChecked}"/>
</StackPanel>
</DockPanel>
</Window>
View and viewmodel
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for ProgressWindow.xaml
/// </summary>
public partial class ProgressWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ProgressWindow()
{
InitializeComponent();
DataContext = this;
}
private int _Progress;
public int Progress
{
get
{
return _Progress;
}
set
{
_Progress = value;
PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
}
}
private bool _IsChecked;
public bool IsChecked
{
get
{
return _IsChecked;
}
set
{
_IsChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
Progress = 0;
await Task.Run(async () =>
{
var value = 0;
while (value < 100)
{
await Task.Delay(500);
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
Progress += 10;
}));
}
});
}
}
}

(WPF) Editable Listbox with Add and Remove buttons

May be I'm missing something, but I think there should be a nice way to add items to ListBoxes whose contents are bound to a data source, ether than having separate button near that ListBox to trigger add_new_item() from the code.
I guess I can figure out how to add a delete button near currently selected button through style. I'm not yet sure which parameter will trigger this. But I'm more concerned with adding items like shown in the image
My Awesome Custom Listbox
I'm not yet advanced into styling. What I do is, I take somebody's style and take it apart, then create what I actually need. I don't even know where to look at to see all styling techniques like looking at a class reference.
I had an idea of adding a button hanging in a corner of a ListBox or ComboBox, but how should I declare a new Event for it so I could assign unique functions to then other than one for everything.
I'm not asking for a complete solution, just a hint of what to do. I'll post an answer when I'll figure it out.
And after a year I've figured these things out.
Just bind to an ObservableCollection and add the value
If that is not what you are looking for please ask a more specific question
You have to create the button yourself and assign a command (MVVM) or click (code behind) to it. Your listbox itemssource should be observablecollection which will notify the UI anytime you add or remove an item from the listbox. If you know that you want a listbox with a button in many places, then you can create a usercontrol out of these two controls. If you want a button inside the lixtbox or combobox then you have to modify the template of these controls, slightly more complicated.
With time I figured these things (UVMCommand is my ICommand interface implementation, it is easier for me to use it this way):
public class Game : Notifiable {
public Game() {
Players = new ObservableCollection<Player>();
AddNewPlayer = new UVMCommand("AddNewPlayer", p => {
var pl = new Player() { Text = "New Player", IsSelected = true, IsEdited = true };
pl.RemoveThis = new UVMCommand("RemoveThis", pp => Players.Remove(pl));
Players.Add(pl);
});
}
private ObservableCollection<Player> _players;
public ObservableCollection<Player> Players { get { return _players; } set { _players = value; OnPropertyChanged("Players"); } }
private UVMCommand _addNewPlayer;
public UVMCommand AddNewPlayer { get { return _addNewPlayer; } set { _addNewPlayer = value; OnPropertyChanged("AddNewPlayer"); } }
}
public class Player : Notifiable {
public Player() {}
private UVMCommand _removeThis;
public UVMCommand RemoveThis { get { return _removeThis; } set { _removeThis = value; OnPropertyChanged("RemoveThis"); } }
}
public class Notifiable : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then making a user control in code-behind declare:
public partial class EditableListBox : System.Windows.Controls.ListBox, INotifyPropertyChanged {
public EditableListBox() {
InitializeComponent();
//var s = FindResource("EditableListBoxStyle") as Style;
//Style = s;
}
[Category("Common")]
public UVMCommand AddItem {
get { return (UVMCommand)GetValue(AddItemProperty); }
set {
SetValue(AddItemProperty, value);
}
}
// Using a DependencyProperty as the backing store for AddItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register("AddItem", typeof(UVMCommand), typeof(EditableListBox),
new PropertyMetadata(
new UVMCommand("Default Command", p => { Debug.WriteLine("EditableListBox.AddITem not defined"); })));
[Category("Layout")]
public Orientation Orientation {
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Using a DependencyProperty as the backing store for Orientation. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(EditableListBox), new PropertyMetadata(Orientation.Vertical));
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in XAML I define a Style with an add button in a template at the end of a list and a delete button in an item template:
<ListBox x:Name="listBox" x:Class="MTCQuest.CustomControls.EditableListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MTCQuest"
xmlns:zq="clr-namespace:MTCQuest.ViewModel.zQuest;assembly=MTCQuest.ViewModel"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:ccon="clr-namespace:MTCQuest.CustomControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Style="{DynamicResource EditableListBoxStyle}"
ItemContainerStyle="{DynamicResource EditableListBoxItemStyle}" d:DataContext="{DynamicResource TestTheme}" ItemsSource="{Binding Questions}">
<ListBox.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/StyleRes.xaml"/>
<ResourceDictionary Source="../Resources/QuestSpacificControlStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ccon:BindingExists x:Key="BindingExists"/>
<ccon:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter"/>
<zq:Theme x:Key="TestTheme">
<zq:Theme.Questions>
<zq:Question IsEdited="True" IsSelected="True" Text="Some Question" Color="#FF2E00FF"/>
<zq:Question Text="Another Question"/>
</zq:Theme.Questions>
</zq:Theme>
<Style x:Key="EditableListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource Pallete.Divider}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{StaticResource Pallete.PrimaryText}"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="True"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ItemsPanel" Value="{DynamicResource OrientedItemsPanelTemplate}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ScrollViewer Background="{TemplateBinding Background}" SnapsToDevicePixels="true" Focusable="false" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="{Binding Orientation, ElementName=listBox}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<ccon:zButton x:Name="AddButton" Command="{Binding AddItem, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalContentAlignment="Left"
Background="Transparent" Foreground="{DynamicResource Pallete.PrimaryText}"
BorderThickness="0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Rectangle Width="16" Height="16" Margin="5,0"
Fill="{DynamicResource Pallete.Accent}"
OpacityMask="{DynamicResource Icon_PlusSign}"/>
<TextBlock Text="Add" Foreground="{StaticResource Pallete.PrimaryText}"/>
</StackPanel>
</ccon:zButton>
</StackPanel>
</ScrollViewer>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="AddButton" Property="Visibility" Value="Visible"/>
</Trigger>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Effect">
<Setter.Value>
<ccon:DesaturateEffect DesaturationFactor=".25"/>
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="EditableListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="5,0"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<Grid Height="33" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="TB" Text="{Binding Text}"
VerticalContentAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="5, 0" BorderThickness="0" GotFocus="TB_GotFocus"
Visibility="Collapsed"/>
<Label x:Name="Lb" Content="{Binding Text}"
VerticalContentAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="5, 0" BorderThickness="0" Margin="2,1,0,0" />
<xctk:ColorPicker x:Name="CB" Grid.Column="1" Width="48" Visibility="Collapsed"
SelectedColor="{Binding Color}"
ShowRecentColors="True" ShowDropDownButton="False" ShowStandardColors="False"/>
<!--<ccon:zButton x:Name="CB" OpacityMask="{DynamicResource Icon_Edit}"
Width="16" Height="16" Panel.ZIndex="19"
Background="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}" BorderThickness="0"
Visibility="Collapsed" Margin="2" Grid.Column="1"/>-->
<ccon:zButton x:Name="DB" OpacityMask="{DynamicResource Icon_MinusSign}"
Command="{Binding RemoveThis}"
Width="16" Height="16" Background="#FFD12929" BorderThickness="0"
Visibility="Collapsed" Margin="2" Grid.Column="2"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!--<DataTrigger Binding="{Binding IsEdited}" Value="true">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TB}" />
</DataTrigger>-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#1F26A0DA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#A826A0DA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#3DDADADA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#FFDADADA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#3D26A0DA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#FF26A0DA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="Lb" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="TB" Value="Visible"/>
<Setter Property="Visibility" TargetName="DB" Value="Visible"/>
<Setter Property="Visibility" TargetName="CB" Value="Visible"/>
</MultiTrigger>
<DataTrigger Binding="{Binding Color, Converter={StaticResource BindingExists}, FallbackValue=false}" Value="false">
<Setter Property="Visibility" TargetName="CB" Value="Collapsed"/>
</DataTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ItemsPanelTemplate x:Key="OrientedItemsPanelTemplate">
<VirtualizingStackPanel IsItemsHost="True"
Orientation="{Binding Orientation, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ccon:EditableListBox}}}"/>
</ItemsPanelTemplate>
</ResourceDictionary>
</ListBox.Resources>
And now it's just a:
<ccon:EditableListBox ItemsSource="{Binding Players}" AddItem="{Binding AddNewPlayer}" Orientation="Horizontal" HorizontalContentAlignment="Center" ScrollViewer.VerticalScrollBarVisibility="Disabled"/>
And Hell Yeagh! I can make it either vertical or horizontal.
I can't belive that when I was just learning WPF I've tried to do something that takes so many things to know and be able to write them (Commands, Templates, Triggers, DependencyProperties, INotifyPropertyChanged and many more), that I had no idea even existed. Just a yaer ago :)

Why do these triggers/events in this custom control working when it inherits from Control but not from Button?

These triggers work like expected when this custom control inherits from Control, but not when it inherits from button. When it inherits from button, the second trigger is never triggered. Also, when inherited from Button, the control will remain in the hovered state (i.e. first trigger is satisfied) if the mouse button remains held down, even if the cursor moves out of the control.
Snippet from Generic.xaml:
<Style TargetType="{x:Type ui:SquareButton}">
<Setter Property="Background" Value="{StaticResource BackgroundColorBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource MainColorBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="0,2,2,0" />
<Setter Property="Padding" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ui:SquareButton}">
<Border x:Name="PART_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" HorizontalAlignment="Center" CornerRadius="{TemplateBinding CornerRadius}">
<Grid Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=AHeight}" Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}">
<ui:ColorableImage x:Name="PART_Image" Source="calendar-black.png" Color="Black" Stretch="None"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource SecondaryColorBrush}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsMouseLeftButtonDown" Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="{StaticResource MainColorBrush}" />
<Setter TargetName="PART_Image" Property="Color" Value="{StaticResource BackgroundColor}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SquareButton.cs:
public class SquareButton : Button
{
static SquareButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SquareButton), new FrameworkPropertyMetadata(typeof(SquareButton)));
}
private static DependencyPropertyKey IsMouseLeftButtonDownPropertyKey = DependencyProperty.RegisterReadOnly("IsMouseLeftButtonDown", typeof(bool), typeof(SquareButton), , new PropertyMetadata());
public static DependencyProperty IsMouseLeftButtonDownProperty = IsMouseLeftButtonDownPropertyKey.DependencyProperty;
public bool IsMouseLeftButtonDown
{
get { return (bool)GetValue(IsMouseLeftButtonDownProperty); }
private set { SetValue(IsMouseLeftButtonDownPropertyKey, value); }
}
public static DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(SquareButton));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Border border = GetTemplateChild("PART_Border") as Border;
if (border != null)
{
border.MouseLeftButtonDown += new MouseButtonEventHandler(Border_MouseLeftButtonDown);
border.MouseLeftButtonUp += new MouseButtonEventHandler(Border_MouseLeftButtonUp);
border.MouseLeave += new MouseEventHandler(Border_MouseLeave);
}
}
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
IsMouseLeftButtonDown = true;
}
private void Border_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
IsMouseLeftButtonDown = false;
}
private void Border_MouseLeave(object sender, MouseEventArgs e)
{
IsMouseLeftButtonDown = false;
}
}
You can use a trigger on the IsPressed property instead of your multitrigger :
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{StaticResource MainColorBrush}" />
<Setter TargetName="PART_Image" Property="Color" Value="{StaticResource BackgroundColor}" />
</Trigger>

How can you share a resource between ListBox item instances?

We have a custom-rendered ListBox which maintains an instance of a StreamGeometry object that is based on its width. The control needs to then share that StreamGeometry instance with all of its items for rendering purposes.
Only way we can think is putting that StreamGeometry instance in the ViewModel for the ListBox, then binding to it in the individual DataTemplates, which just feels dirty to me considering that is a view-only thing and therefore shouldn't be in the ViewModel at all.
Note: We could also just store it via an attached property on the ListBox (or subclass the ListBox), but we're still left with binding of a view-only thing which seems wrong to me for something like this.
Any thoughts?
You can make the StreamGeometry a dependency property on your custom listview, then refer to it through Binding MyGeometry, RelativeSource={RelativeSource AncestorType=ListView}.
This way, there is no ViewModel involved.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- default lsitviewitem style except for added path -->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- added path-->
<Path Stretch="Uniform" Stroke="DarkBlue" Fill="DarkOrchid" Data="{Binding MyGeometry, RelativeSource={RelativeSource AncestorType=ListView}}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid >
<local:CustomListView Margin="20" >
<local:CustomListView.Items>
<ListViewItem Content="ListViewItem1" />
<ListViewItem Content="ListViewItem2" />
<ListViewItem Content="ListViewItem3" />
</local:CustomListView.Items>
</local:CustomListView>
</Grid>
</Window>
CustomListView:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication1
{
public class CustomListView : ListView
{
public StreamGeometry MyGeometry { get { return (StreamGeometry)GetValue(MyGeometryProperty); } set { SetValue(MyGeometryProperty, value); } }
public static readonly DependencyProperty MyGeometryProperty = DependencyProperty.Register("MyGeometry", typeof(StreamGeometry), typeof(CustomListView), new PropertyMetadata(null));
protected override void OnRender(DrawingContext drawingContext)
{
StreamGeometry geometry = new StreamGeometry(); // directly opening MyGeometry results in "must have isfrozen set to false to modify" error
using (StreamGeometryContext context = geometry.Open())
{
Point p1 = new Point(this.ActualWidth * (2d / 5d), 0);
Point p2 = new Point(this.ActualWidth / 2d, -10);
Point p3 = new Point(this.ActualWidth * (3d / 5d), 0);
context.BeginFigure(p1, true, true);
List<Point> points = new List<Point>() { p2, p3 };
context.PolyLineTo(points, true, true);
}
drawingContext.DrawGeometry(Brushes.DarkOrchid, new Pen(Brushes.DarkBlue, 1), geometry);
this.MyGeometry = geometry;
base.OnRender(drawingContext);
}
}
}

Resources