I have a DataGridColumnHeader Style that has a TextBox situated above the DataGridColumn. In the TextBox Style, there is a DataTrigger that sets its visibility based on the Text in the DataGridColumn. Rather than checking if its value equals "Pos", I would like to use a converter to see if its value begins with the string "Pos". But when I add the converter, the value parameter in the converter turns out not being a string. Instead it is the MainWindow object that the datagrid belongs to. Why would it take on a different meaning when I add a converter?
<Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderStyle"
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<Style.Resources>
<SolidColorBrush x:Key="borderBackground">#E4E5ED</SolidColorBrush>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border Background="{StaticResource borderBackground}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Margin="1" Grid.Column="0" Grid.Row="0" IsReadOnly="True"
Text="{Binding Path=DataContext.TotalPos, StringFormat=N0, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type DataGrid}}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="Pos">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<theme:DataGridHeaderBorder Grid.Column="0" Grid.Row="1" SortDirection="{TemplateBinding SortDirection}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
IsClickable="{TemplateBinding CanUserSort}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding ="{TemplateBinding Padding}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
SeparatorBrush="{TemplateBinding SeparatorBrush}">
<TextBlock Grid.Column="0" Grid.Row="1" Text="{TemplateBinding Content}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextWrapping="Wrap"></TextBlock>
</theme:DataGridHeaderBorder>
<Thumb Grid.Column="0" Grid.Row="1" Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}"/>
<Thumb Grid.Column="0" Grid.Row="1" Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With converter:
<src:StartsWith x:Key="startsWith"/>
...
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource startsWith}, ConverterParameter=Pos}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
[ValueConversion(typeof(string), typeof(bool))]
public class StartsWith : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value).StartsWith((string)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Not Implemented.");
}
}
It doesn't know what to bind to...add Binding RelativeSource={RelativeSource Self}, Path=Text for the converter.
I bind with this, thanks
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridColumnHeader, AncestorLevel=1}, Path=Column.Header, Converter={StaticResource startsWith}, ConverterParameter=Pos}" Value="True">
Related
How would you design a Google images -like component?
I'm not sure how to handle the detail panel which shows under each image when selected.
The detail panel:
- shows between two rows of images.
- keep left and right items in place.
SOLUTION:
Thanks to LittleBit for providing a very nice solution to the problem. Based on his hints, i created the following general-purpose component taking care of a few details you will eventually face when making LittleBit solution production-ready.
file: DetailedList.cs
public class DetailedList : ListBox
{
#region DetailsTemplate
public DataTemplate DetailsTemplate
{
get { return (DataTemplate)GetValue( DetailsTemplateProperty ); }
set { SetValue( DetailsTemplateProperty, value ); }
}
public static readonly DependencyProperty DetailsTemplateProperty =
DependencyProperty.Register( nameof( DetailsTemplate ), typeof( DataTemplate ), typeof( DetailedList ) );
#endregion
static DetailedList()
{
Type ownerType = typeof( DetailedList );
DefaultStyleKeyProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( ownerType ) );
StyleProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( null, ( depObj, baseValue ) =>
{
var element = depObj as FrameworkElement;
if( element != null && baseValue == null )
baseValue = element.TryFindResource( ownerType );
return baseValue;
} ) );
}
}
file: StretchGrid.cs
internal class StretchGrid : Grid
{
private Expander _expander;
#region ParentPanel
public Panel ParentPanel
{
get { return (Panel)this.GetValue( ParentPanelProperty ); }
set { this.SetValue( ParentPanelProperty, value ); }
}
public static readonly DependencyProperty ParentPanelProperty = DependencyProperty.Register(
nameof( ParentPanel ), typeof( Panel ), typeof( StretchGrid ), new PropertyMetadata( null, ParentPanelChanged ) );
private static void ParentPanelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var stretchGrid = d as StretchGrid;
if( stretchGrid == null ) return;
if( e.NewValue is Panel panel )
panel.SizeChanged += stretchGrid.UpdateMargins;
}
#endregion
public StretchGrid()
{
this.Loaded += StretchGrid_Loaded;
}
private void StretchGrid_Loaded( object sender, RoutedEventArgs e )
{
_expander = this.FindLogicalParent<Expander>();
_expander.Expanded += UpdateMargins;
_expander.SizeChanged += UpdateMargins;
this.UpdateMargins( null, null );
}
private void UpdateMargins( object sender, RoutedEventArgs e )
{
if( ParentPanel == null ) return;
if( _expander == null ) return;
Point delta = _expander.TranslatePoint( new Point( 0d, 0d ), ParentPanel );
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full row under the item)
this.Margin = new Thickness( -delta.X, 0, delta.X + _expander.ActualWidth - ParentPanel.ActualWidth, 0 );
}
}
file: DetailedList.xaml
<Style x:Key="{x:Type cc:DetailedList}" TargetType="{x:Type cc:DetailedList}"
BasedOn="{StaticResource {x:Type ListBox}}">
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoButtonExpander.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
<Setter Property="Grid.IsSharedSizeScope" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"
Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" SnapsToDevicePixels="True">
<Expander Style="{StaticResource NoButtonExpander}" VerticalAlignment="Top"
IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}">
<Expander.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="A"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
</Grid>
</Expander.Header>
<cc:StretchGrid ParentWrapPanel="{Binding Path=Tag,
RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}">
<ContentPresenter ContentTemplate="{Binding DetailsTemplate,
RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"/>
</cc:StretchGrid>
</Expander>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
file: NoButtonExpander.xaml
<Style x:Key="ExpanderRightHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderUpHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderLeftHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderHeaderFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="NoButtonExpander" TargetType="{x:Type Expander}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" Style="{StaticResource ExpanderDownHeaderStyle}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
<ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderRightHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderUpHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderLeftHeaderStyle}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My approach is to use a ListView with some modifications
The ItemsPanel of the ListView must have a 'wrap' function, so i used a Wrappanel to display the Image-Previews in a field.
The ItemContainter of the ListViewItem must have two states. An Image-Preview state and a Detail-View therfore an Expander should do the trick.
The Details of an Image must be displayed across the whole Column under the Image. We need something which renders outside the Boundaries of a ListViewItem (more later)
It now looks like this
The Image Details are now messed up due to the Boundaries. The Height is no problem because it just moves the next Column down until it fits in. The Problem is the Width and its Position (its not left aligned).
I created a small CustomControl, the StretchGrid which renders its Content across the whole Column and left aligned. This StretchGrid get the relative distance to the left Boundary and sets it as negative Margin.Left to render it properly. Now it looks like this (and i hope that is exactly what you are looking for).
Now the Style of the ListView
<!-- Image List with Detail -->
<Style x:Key="PicList" TargetType="{x:Type ListView}">
<!-- Only one Picture can be selected -->
<Setter Property="SelectionMode" Value="Single"/>
<!-- Enable Multi-Line with a WrapPanel Around the Items -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (ListView.ActualWidth),RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Override Display area of the Item -->
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<!-- Define Image Item -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- Use Expander Header as Preview/Thumbnail and display Details below when expanded -->
<Expander x:Name="PicThumbnail" Style="{StaticResource NoButtonExpander}" IsExpanded="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}" Width="{Binding (ListViewItem.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
<Expander.Header>
<!-- Thumbnail/Preview Section -->
<StackPanel>
<Image Source="/XAML;component/Assets/Images/Thumb.png" Height="16" Width="16" />
<Label Content="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<!-- Self stretching Grid (Custom Control) -->
<cc:StretchGrid x:Name="PicDetails" Background="LightGray" ParentWrappanel="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}}">
<!-- Picture Detail Section (quick & dirty designed, replace later) -->
<Image ClipToBounds="False" Source="/XAML;component/Assets/Images/Highres.png" Width="128" Height="128" HorizontalAlignment="Left" Margin="10,0" />
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Left"/>
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Right"/>
<StackPanel Margin="150,0">
<Label Content="{Binding Name}"/>
<Label Content="Description: Lorem"/>
<Label Content="Category: Ipsum"/>
<Label Content="Owner: Dolor"/>
<Label Content="Size: 5kB"/>
</StackPanel>
</cc:StretchGrid>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Brings selected element to front, details see edit -->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
Note: Its necessary to add a Reference to the StretchGrid.
And the StretchGrid Custom Control
class StretchGrid : Grid
{
//Reference for parent expander (used to calculate Grid Margin)
private Expander m_expander;
//Property for relativesource Binding to Wrappanel in Style
public WrapPanel ParentWrappanel
{
get { return (WrapPanel)this.GetValue(Wrappanel); }
set { this.SetValue(Wrappanel, value); }
}
//DependencyProperty for RelativeSource Binding to Wrappanel in Style (Note: the Binding is set inside the Style, not here programmatically in PropertyMetaData)
public static readonly DependencyProperty Wrappanel = DependencyProperty.Register("ParentWrappanel", typeof(WrapPanel), typeof(StretchGrid), new PropertyMetadata(null));
//Constructor
public StretchGrid() : base()
{
Application.Current.MainWindow.Loaded += Init;
Application.Current.MainWindow.SizeChanged += UpdateMargins;
}
private void Init(object sender, RoutedEventArgs e)
{
m_expander = (Expander)this.Parent; //Change when xaml markup hirarchy changes
if(m_expander != null) //(or make it similar to the Wrappanel with
{ //RelativeSource Binding)
m_expander.Expanded += UpdateMargins; //Update when expander is expanded
m_expander.SizeChanged += UpdateMargins; //Update when the expander changes the Size
//Update all StretchGrids on Initialization
UpdateMargins(null, null);
}
}
//Calculate Grid Margin when an according Event is triggered
private void UpdateMargins(object sender, RoutedEventArgs e)
{
if(ParentWrappanel != null)
{
Point delta = m_expander.TranslatePoint(new Point(0d, 0d), ParentWrappanel);
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full column under the Image)
this.Margin = new Thickness(-delta.X, 0, delta.X + m_expander.ActualWidth - ParentWrappanel.ActualWidth, 0);
//Theese Values arent calculated exavtly, just broad for example purpose
}
}
}
This is a little bit tricky to incorporate in existing Code. A working Example may help and is found HERE.
SideNote: If i got more time i would pack this stuff into a bigger Custom Control to use it like a normal UIElement (eg. Border). This would improve the re-usability and usability quite a lot.
EDIT
The 'newest' element in the ListView is also atop of the Z-Index, therefore it is 'above' the expanded StretchGrid and some Controls can not be clicked because they are 'behind' the next ListviewItem.
To fix this, add
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
to the ListViewItem Style. Now when its visible it will place itself atop the other Controls.
This is what I have done so far:-
Images listed:
An Image Clicked - will show the Details view below it:
At the moment I am not closing other Detail Views - so below can happen for me.
However, all this needs is to propagate an event that some other item has been clicked. and close all other Detail Views (its mvvm and driven by a bool property)
======================================================
I have a Base UserControl which will load Items in a Top Down manner:
<Grid>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding GridData, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
Each Row (Item) of this UserControl itself uses another ItemsControl. So that Images are lined up horizontally. Also, Each Item has a Details View Section (Border with Visibility controlled by Image clicks)
<UserControl.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding ImagesList, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5" Command="{Binding DataContext.ItemClickedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Image, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Name="DisplayText" Grid.Row="1" HorizontalAlignment="Center" Text="{Binding DisplayText, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Margin="2" Grid.Row="1" Background="Black" Padding="10"
Visibility="{Binding ShowDetailsPanel, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="White" CornerRadius="10">
<Image Margin="5" Source="{Binding SelectedImage.Image, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.ImageName, StringFormat={}Filename: {0}}" Foreground="White"/>
<TextBlock Grid.Row="1" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Size, StringFormat={}Size: {0} bytes}" Foreground="White"/>
<TextBlock Grid.Row="2" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Location, StringFormat={}Path: {0}}" Foreground="White"/>
<TextBlock Grid.Row="3" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.CreatedTime, StringFormat={}Last Modified: {0}}" Foreground="White"/>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>
Ok so i have this properties in my View Model:
public double Progress
{
get { return _progress; }
set
{
_progress= value;
OnPropertyChanged();
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
And as you can see this implement INotifyPropertyChanged.
This is my Progress-Bar:
<ProgressBar Name="progressBarColumn"
Minimum="0"
Maximum="100"
Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding Path=Width, ElementName=ProgressCell}"
Style="{StaticResource CustomProgressBar2}"/>
And my Style:
<Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="Blue"
BorderBrush="Blue"
BorderThickness="1"/>
<Border Name="PART_Indicator"
CornerRadius="0"
Background="Gray"
BorderBrush="Gray"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So this works fine, my Progress-Bar color is Blue and fill with Gray color.
Now i want to add two more things and dont know when:
1. When my Progress-Bar reach the Value of 100 i want it Background become Green.
When my Property called IsChecked is False i want that my Background become Red.
So until here i have this IMultiValueConverter:
public class ProgressToPropgressBarBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Do my stuff
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem is that i dont know where to add my converter and how.
EDIT:
<Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
<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>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource AncestorType=ProgressBar}}">
<Setter Property="Foreground" Value="DarkCyan"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked}" Value="true">
<Setter Property="Background" Value="{DynamicResource ProgressBackgroundColor}" />
<Setter Property="Foreground" Value="{DynamicResource ProgressBarFillColor}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked}" Value="false">
<Setter Property="Background" Value="#55B3B3B6" />
</DataTrigger>
</Style.Triggers>
</Style>
You logic is simple enough you can avoid a converter and simply use triggers in your style:
<Style.Triggers>
<DataTrigger Binding=“{Binding Progress}” Value=“100”>
<Setter Property=“Background” Value=“Green” />
</DataTrigger>
<DataTrigger Binding=“{Binding IsChecked}” Value=“True”>
<Setter Property=“Background” Value=“Red” />
</DataTrigger>
</Style.Triggers>
Then you need to update your control template to properly respect the control’s Background property. You can use TemplateBinding for this:
<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" />
And if you want the same Blue and Gray default colors, add them to the style:
<Setter Property=“Background” Value=“Blue”/>
<Setter Property=“Foreground” Value=“Gray”/>
Here is the intention,
When items get added to ItemsControl and if the content is more than the available size I wish to see scrolling in ItemsControl. I bet this is the default behavior for any ItemsControl type.
So I generated a sample replicates my intention. Suggest me what I need to fix
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<ToggleButton Name="SomeToggle" Content="Show/Hide"/>
<Button Content="Add Item" Click="ButtonBase_OnClick" />
<TextBlock Text="Hide/Show on toggle" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked,ElementName=SomeToggle}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ItemsControl Name="ItemsCollection" >
</ItemsControl>
</StackPanel>
and Code behind:
private int counter = 0;
private ObservableCollection<string> coll;
public MainWindow()
{
coll= new ObservableCollection<string>();
//ItemsCollection.ItemsSource = coll;
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ItemsCollection.Items.Add(string.Format("Item {0}", ++counter));
}
This is basically occurs each time you put a ScrollViewer inside a Stackpanel, and this is due to the way the Stackpanel works, take a look here
you could either put the Stackpanel inside a ScrollView, or better change your main panel to a Grid
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ToggleButton Name="SomeToggle" Content="Show/Hide" Grid.Row="0"/>
<Button Content="Add Item" Click="ButtonBase_OnClick" Grid.Row="1"/>
<TextBlock Text="Hide/Show on toggle" Grid.Row="2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked,ElementName=SomeToggle}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ItemsControl Name="ItemsCollection" Grid.Row="3">
</ItemsControl>
</Grid>
The only reason you are not able to see the scrollbars cause you have not set the height of your ItemsControl.
Every time you add a item it's height will expand and StackPanel will expand with it to contain the content. You have already defined ScrollViewer to your ItemsControl template you have to also decide at what point it has to work.
<ItemsControl Name="ItemsCollection" Height="200" >
</ItemsControl>
Then it will also not matter either you containing it in a StackPanel or any other control.
initial scroll bar
After adding items
with code
And just from understanding point of view. this will also work:
Remove style for item control & set the height of scroll viewer.
To handle the height of all elements:
<ScrollViewer MaxHeight="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}},Path=ActualHeight,Mode=OneWay}">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<ToggleButton Name="SomeToggle" Content="Show/Hide"/>
<Button Content="Add Item" Click="Button_Click" />
<TextBlock Text="Hide/Show on toggle" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked,ElementName=SomeToggle}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ItemsControl Name="ItemsCollection" ItemsSource="{Binding list}"
MaxHeight="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}},Path=ActualHeight,Mode=OneWay}">
</ItemsControl>
</StackPanel>
</ScrollViewer>
I just created an Expander style with a checkbox in the header:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Background" Value="{StaticResource ExpanderBackgroundBrush}"/>
<Setter Property="HeaderTemplate" Value="{StaticResource MyHeaderExpander}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" x:Name="ContentRow"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" x:Name="Border" Background="{TemplateBinding Background}" BorderThickness="1"
BorderBrush="{StaticResource ExpanderBorderBrush}" CornerRadius="2,2,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2"/>
<ToggleButton Grid.Column="2" x:Name="ToggleBt" Template="{DynamicResource MyExpanderToggleButton}" Background="{StaticResource ExpanderBackgroundBrush}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
OverridesDefaultStyle="True"/>
<ContentPresenter Grid.Column="1" Margin="0,0,0,0" RecognizesAccessKey="True" ContentSource="Header"/>
</Grid>
</Border>
<Border Visibility="Collapsed" Grid.Row="1" x:Name="ExpandSite" Background="{StaticResource ContentBackgroundBrush}"
BorderBrush="{StaticResource ExpanderBorderBrush}" BorderThickness="1,0,1,1" CornerRadius="0,0,2,2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Focusable="false"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" Value="Visible" TargetName="ExpandSite"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource ExpanderBackgroundBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ExpanderBackgroundBrush}" TargetName="Border"/>
<Setter Property="Visibility" Value="Hidden" TargetName="ToggleBt" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I create a new property (IsChecked) for this expander in order to bind to a boolean value? e. g:
<Expander IsChecked="{Binding MyBoolValue}" />
Also on the template:
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2" IsChecked="{TemplateBinding ????}" />
EDIT:
I found a similar question but I want to know if there's another way to do it without using control inheritance.
I created an Attached Property like this:
public sealed class AP : DependencyObject
{
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool), typeof(AP), new UIPropertyMetadata(false));
}
Then all I had to do was:
<Expander my:AP.IsChecked="{Binding Path=TestBool}" />
And on the template:
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
I wonder what to do if the style was defined in a different assembly tough...
I have a ControlTemplate which uses the same color in multiple elements. On certain triggers (e.g. OnMouseOver) I'd like to change that color. As far as I can see I have to define a setter for every element to change its color. Is there a way to reference a shared resource in the template that all contained elements can access, and which can be changed by a trigger, so I don't have to address each and every element?
Here's an (made up) example:
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Fill="red" Grid.Column="0"/>
<Ellipse Fill="red" Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
When the control is disabled, I want the ellipses to be grey, without setting both of them explicitly, e.g. I don't want to write
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="_ellipse1" Property="Fill" Value="Grey"/>
<Setter TargetName="_ellipse2" Property="Fill" Value="Grey"/>
</Trigger>
but set the color of both ellipses with just one setter.
Place the trigger on a style for the ellipses (ellipsi?) instead of the button. IsEnabled will propagate down if you set IsEnabled = false on the Button.
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type Ellipse}">
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Fill" Value="Gray" />
</Trigger>
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"/>
<Ellipse Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
I think the best way to do this is with a value converter. Then you can avoid a messy trigger altogether. Here is your example, but with a converter added.
<Window.Resources>
<local:EnabledToColorConverter x:Key="enabledToColorConverter"/>
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Name="_ellipse1" Fill="{TemplateBinding IsEnabled, Converter={StaticResource enabledToColorConverter}}" Grid.Column="0"/>
<Ellipse Name="_ellipse2" Fill="{TemplateBinding IsEnabled, Converter={StaticResource enabledToColorConverter}}" Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Template="{StaticResource myTemplate}">Enabled Button</Button>
<Button Template="{StaticResource myTemplate}" IsEnabled="False">Disabled Button</Button>
</StackPanel>
And here is the converter:
public class EnabledToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isEnabled = (bool)value;
return isEnabled ?
Brushes.Red :
Brushes.Gray;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}