Change button appearance/icon - c#

I want to change the icon on my button. Here is the xaml of my button:
<Button Name="InitPurgeBtn" Click="InitPurgeClick">
<Rectangle Width="35" Height="45" Margin="0,0,0,0">
<Rectangle.Fill>
<VisualBrush Stretch="Fill" Visual="{StaticResource InitIcon}" />
</Rectangle.Fill>
</Rectangle>
</Button>
The probleme is I don't know how to acces the Visual property of the Rectangle of my button in my controller to change "InitIcon" by "PurgeIcon"
All my icon are implement in xaml:
<Viewbox x:Key="ExtinctionIcon" Stretch="Uniform" x:Shared="False">
<Canvas Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path Width="40" Height="40" Stretch="Fill" Fill="{Binding Foreground,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}"
Data="M15,24H17V22H15M16.56,{...}24H13V22H11M7,24H9V22H7V24Z"/>
</Canvas>
</Viewbox>
Edit :
I change my button and he is like this now :
<Button Name="InitBtn" Style="{StaticResource RoundButton}" Width="70" Height="70"
Click="InitPurgeClick" Content="{StaticResource InitIcon}">
</Button>
I want to change the icon in my code so I try to set Content property like this :
InitBtn.Content = "{StaticResource ExtinctionIcon}";
But this way just replace my icon with the string "StaticResource.."

Updated to reflect new information
You updated your question with some new information that changes things a lot.
Since you are trying to update Button.Content in code, you won't be able to use the MarkupExtension. MarkupExtensions (the stuff inside { } in the XAML) are only evaluated when the view is initially created. After that, they behave just like regular strings, so trying to set one in code won't work.
To set the value in code, you will need to manually do what the MarkupExtension is doing; find the resource by name, and set the value directly. You can do this with the following code (assuming a reference to InitPurgeBtn).
InitPurgeBtn.Content = InitPurgeBtn.FindResource("ExtinctionIcon");
Previous Answer
You should be able to add your icon to the Content of the Button directly, as it seems to be defined as a resource somewhere (because of the x:Key attribute). Your code doesn't show where exactly that is though, so I can't grantee this will work without some modification.
<Button Name="InitPurgeBtn"
Width="100"
Height="40"
Content="{StaticResource ExtinctionIcon}" />
In order for that to work the ExtinctionIcon resource will have to be defined someplace accessible to the button, meaning either in an ancestor of the button, or in App.xaml.
The fact that the resource is defined with x:Shared="false" seems to indicate that it was designed to be used in exactly this way, as that is required for visual elements that can possibly be hosted in multiple places simultaneously.
Alternatively, you could just copy and embed the icon directly in to the button.
<Button Name="InitPurgeBtn"
Click="InitPurgeClick">
<Viewbox Stretch="Uniform">
<Canvas Width="76"
Height="76"
Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path Width="40"
Height="40"
Stretch="Fill"
Fill="{Binding Foreground,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Button}}"
Data="M15,24H17V22H15M16.56,{...}24H13V22H11M7,24H9V22H7V24Z"/>
</Canvas>
</Viewbox>
</Button>

You may use Content property.
<Button Name="InitPurgeBtn" Width="100" Height="40">
<Button.Content>
<Image Source=".\Icon.PNG"/>
</Button.Content>
</Button>

If you just want a single (xaml) icon, you can bind the content property of your button to your icon resource.
The control below gives an enhanced display - a xaml icon plus a text caption, including two colours for the icon and support for disabled state.
MyXamlIconHost.cs
public enum CaptionPosition { None, ToLeftOfIcon, AboveIcon, ToRightOfIcon, BelowIcon }
public enum IconSize { Small, Medium, Large, XLarge, XxLarge }
public class myXamlIconHost : Control
{
private static readonly Brush DefaultForeground = new SolidColorBrush(Color.FromRgb(32,32,32));
private static readonly Brush DefaultHighlight = Brushes.DarkOrange;
private static readonly Brush DefaultDisabledForeground = new SolidColorBrush(Color.FromRgb(192, 192, 192));
private static readonly Brush DefaultDisabledHighlight = new SolidColorBrush(Color.FromRgb(128, 128, 128));
static myXamlIconHost()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(myXamlIconHost), new FrameworkPropertyMetadata(typeof(myXamlIconHost)));
}
public FrameworkElement XamlIcon
{
get { return (FrameworkElement)GetValue(XamlIconProperty); }
set { SetValue(XamlIconProperty, value); }
}
public static readonly DependencyProperty XamlIconProperty =
DependencyProperty.Register("XamlIcon", typeof(FrameworkElement), typeof(myXamlIconHost), new PropertyMetadata(null));
public IconSize IconSize
{
get { return (IconSize)GetValue(IconSizeProperty); }
set { SetValue(IconSizeProperty, value); }
}
public static readonly DependencyProperty IconSizeProperty =
DependencyProperty.Register("IconSize", typeof(IconSize), typeof(myXamlIconHost), new PropertyMetadata(IconSize.Medium));
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(myXamlIconHost), new PropertyMetadata(null));
public CaptionPosition CaptionPosition
{
get { return (CaptionPosition)GetValue(CaptionPositionProperty); }
set { SetValue(CaptionPositionProperty, value); }
}
public static readonly DependencyProperty CaptionPositionProperty =
DependencyProperty.Register("CaptionPosition", typeof(CaptionPosition), typeof(myXamlIconHost), new PropertyMetadata(CaptionPosition.ToRightOfIcon));
public Brush StandardForeground
{
get { return (Brush)GetValue(StandardForegroundProperty); }
set { SetValue(StandardForegroundProperty, value); }
}
public static readonly DependencyProperty StandardForegroundProperty =
DependencyProperty.Register("StandardForeground", typeof(Brush), typeof(myXamlIconHost), new PropertyMetadata(DefaultForeground));
public Brush StandardHighlight
{
get { return (Brush)GetValue(StandardHighlightProperty); }
set { SetValue(StandardHighlightProperty, value); }
}
public static readonly DependencyProperty StandardHighlightProperty =
DependencyProperty.Register("StandardHighlight", typeof(Brush), typeof(myXamlIconHost), new PropertyMetadata(DefaultHighlight));
public Brush DisabledForeground
{
get { return (Brush)GetValue(DisabledForegroundProperty); }
set { SetValue(DisabledForegroundProperty, value); }
}
public static readonly DependencyProperty DisabledForegroundProperty =
DependencyProperty.Register("DisabledForeground", typeof(Brush), typeof(myXamlIconHost), new PropertyMetadata(DefaultDisabledForeground));
public Brush DisabledHighlight
{
get { return (Brush)GetValue(DisabledHighlightProperty); }
set { SetValue(DisabledHighlightProperty, value); }
}
public static readonly DependencyProperty DisabledHighlightProperty =
DependencyProperty.Register("DisabledHighlight", typeof(Brush), typeof(myXamlIconHost), new PropertyMetadata(DefaultDisabledHighlight));
}
// ==============================================================================================================================================
public class myXamlIconSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
const int defaultSize = 24;
if (!(value is IconSize))
return defaultSize;
var iconSizeValue = (IconSize)value;
switch (iconSizeValue)
{
case IconSize.Small:
return defaultSize * 2 / 3;
case IconSize.Large:
return defaultSize * 3 / 2;
case IconSize.XLarge:
return defaultSize * 2;
case IconSize.XxLarge:
return defaultSize * 5 / 2;
default:
return defaultSize;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
MyXamlIconHost.xaml
<Style TargetType="{x:Type ctrl:myXamlIconHost}">
<Setter Property="Focusable" Value="False" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Padding" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:myXamlIconHost}">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<ctrl:myXamlIconSizeConverter x:Key="IconSizeConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="PART_CaptionTextBlock"
Grid.Row="1"
Grid.Column="0"
Margin="8,0,8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{TemplateBinding StandardForeground}"
Text="{TemplateBinding Caption}" />
<!-- Set DataContext to "self" so that the Xaml Icon item can bind to the Foreground and BorderBrush properties -->
<ContentControl x:Name="PART_IconPresenter"
Grid.Row="1"
Grid.Column="1"
Width="{TemplateBinding IconSize,
Converter={StaticResource IconSizeConverter}}"
Height="{TemplateBinding IconSize,
Converter={StaticResource IconSizeConverter}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderBrush="{TemplateBinding StandardHighlight}"
Content="{TemplateBinding XamlIcon}"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Focusable="False"
Foreground="{TemplateBinding StandardForeground}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="PART_CaptionTextBlock" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DisabledForeground}" />
<Setter TargetName="PART_IconPresenter" Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DisabledHighlight}" />
<Setter TargetName="PART_IconPresenter" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DisabledForeground}" />
</Trigger>
<Trigger Property="CaptionPosition" Value="None">
<Setter TargetName="PART_CaptionTextBlock" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="CaptionPosition" Value="ToLeftOfIcon">
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Column" Value="0" />
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Row" Value="1" />
<Setter TargetName="PART_CaptionTextBlock" Property="Margin" Value="8,0,8,0" />
</Trigger>
<Trigger Property="CaptionPosition" Value="ToRightOfIcon">
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Column" Value="2" />
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Row" Value="1" />
<Setter TargetName="PART_CaptionTextBlock" Property="Margin" Value="8,0,8,0" />
</Trigger>
<Trigger Property="CaptionPosition" Value="AboveIcon">
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Column" Value="1" />
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Row" Value="0" />
<Setter TargetName="PART_CaptionTextBlock" Property="Margin" Value="8,0,8,4" />
</Trigger>
<Trigger Property="CaptionPosition" Value="BelowIcon">
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Column" Value="1" />
<Setter TargetName="PART_CaptionTextBlock" Property="Grid.Row" Value="2" />
<Setter TargetName="PART_CaptionTextBlock" Property="Margin" Value="8,4,8,0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
Example Usage
<Window ... >
<Window.Resources>
<Grid x:Key="TestIcon" x:Shared="False" Background="Transparent">
<Path Stretch="Fill" Data=" M 0,0 M 100,100 M 0,0 L 0,100 L 100,100 L 100,60 L 90,60 L 90,90 L 10,90 L 10,10 L 90,10 L 90,40 L 100,40 L 100,0 z" Fill="{ Binding Foreground, FallbackValue=Cyan}"/>
<Path Stretch="Fill" Data=" M 0,0 M 100,100 M 70,45 L 100,45 L 100,55 L 70,55 z" Fill="{ Binding BorderBrush, FallbackValue=Magenta}"/>
</Grid>
</Window.Resources>
<Button HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Background="LightBlue">
<ctrls:myXamlIconHost Caption="The Caption" XamlIcon="{StaticResource TestIcon}" IconSize="XxLarge" Padding="20" />
</Border>
</Button>
</Window>

Related

Must have non-null value for 'Binding' problem

I have made a custom Control in WPF which is a star rating. When there are 5 stars, these should be golden.
<UserControl x:Class="Lama.Wpf.Controls.RatingControl"
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:controls="clr-namespace:Lama.Wpf.Controls"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="RatingTemplate" TargetType="{x:Type ToggleButton}">
<Viewbox>
<Path Name="star" Fill="White" Opacity="0.2"
Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z" />
</Viewbox>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="IsChecked" Value="True" />
<Condition
Binding="{Binding Rating, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:RatingControl}}"
Value="5" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Tag="1" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="1" Tag="2" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="2" Tag="3" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="3" Tag="4" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="4" Tag="5" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
</Grid>
</UserControl>
public partial class RatingControl
{
private const int Max = 5;
public static readonly DependencyProperty RatingProperty = DependencyProperty.Register(nameof(Rating),
typeof(int),
typeof(RatingControl),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
RatingChanged));
public int Rating
{
get => (int)GetValue(RatingProperty);
set
{
if (value < 0)
{
SetValue(RatingProperty, 0);
}
else if (value > Max)
{
SetValue(RatingProperty, Max);
}
else
{
SetValue(RatingProperty, value);
}
}
}
public RatingControl()
{
InitializeComponent();
}
private static void RatingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = sender as RatingControl;
var newVal = (int)e.NewValue;
var children = ((Grid)(item.Content)).Children;
ToggleButton button;
for (var i = 0; i < newVal; i++)
{
button = children[i] as ToggleButton;
if (button != null)
button.IsChecked = true;
}
for (var i = newVal; i < children.Count; i++)
{
button = children[i] as ToggleButton;
if (button != null)
button.IsChecked = false;
}
}
private void ClickEventHandler(object sender, RoutedEventArgs args)
{
var button = sender as ToggleButton;
if (button == null)
{
return;
}
var newValue = int.Parse(button.Tag.ToString());
Rating = newValue;
}
}
If I run this, I get this exception:
InvalidOperationException: Must have non-null value for 'Binding'.
Do I bind something wrong inside my condition? Because if I remove the Rating-Binding, it works, but I don't see my mistake here.
There are multiple issues in your Condition bindings:
The first binding in the MultiDataTrigger uses a Condition that sets Property, which is wrong.
For a MultiDataTrigger, each condition in the collection must set both the Binding and Value properties. For more information, see Binding.
Set the Binding property using a RelativeSource binding to Self instead.
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True"/>
The second binding to Rating is faulty, since it would only ever set any star to be golden and opaque if the rating is exactly five stars. So no stars are selected for any other rating.
In order resolve these issues, you can use a simple Trigger on the IsChecked property.
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</Trigger>
</ControlTemplate.Triggers>
If it is really your requirement to only make the stars golden and opaque if the rating is exactly 5, then you could correct the MultiDataTrigger as stated above:
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True"/>
<Condition Binding="{Binding Rating, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:RatingControl}}" Value="5"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
Another note on value coercion. As #Clemens stated in the comments, setting a property in XAML e.g. like below will bypass your setter and
call SetValue directly. Your setter should only call SetValue as the behavior will differ otherwise when setting properties in XAML or through the property.
<local:RatingControl Rating="{Binding SomeProperty}"/>
Instead of the checks in the setter, you can specify a value coercion callback in the dependency property declaration.
public static readonly DependencyProperty RatingProperty = DependencyProperty.Register(
nameof(Rating), typeof(int), typeof(RatingControl), new FrameworkPropertyMetadata(
0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RatingChanged, CoerceRating));
Then create a CoerceRating method that contains your checks and returns the corresponding values.
private static object CoerceRating(DependencyObject d, object baseValue)
{
var value = (int)baseValue;
if (value < 0)
{
return 0;
}
if (value > Max)
{
return Max;
}
return value;
}
Finally, remove all checks from the setter of Rating.
public int Rating
{
get => (int)GetValue(RatingProperty);
set => SetValue(RatingProperty, value);
}
The value coercion callback will automatically be called, when the property is set through SetValue and therefore ensure that the Rating value is within the valid interval.

Sliding text in combobox

I would like to slide the text (marquee text) of the selected item in a combobox, if it's lenght is bigger than the width of the combobox. It can be either automatical or when the user put the mouse over the combobox. The problem is that i have absolutely no idea on how to do that. It's maybe possible to do that with a render transform (previous definition of a textblock inside it)? or with a storyboard?
Here is the xaml that i need to modify
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate DataType="DataGridColumnHeader" >
<ComboBox ItemContainerStyle="{StaticResource SingleSelectionComboBoxItem}" DisplayMemberPath="Oggetto" Width="100" Height="20" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.Selezione, UpdateSourceTrigger=LostFocus}" SelectionChanged="SingleSelectionComboBox_SelectionChanged"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
EDIT: the problem is that i don't know which properties should i target in the storyboard
EDIT2: i took the template of the combobox and modified the text box section like this :
<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="100" Duration="00:00:10" Storyboard.TargetProperty="X" Storyboard.TargetName="transferCurreny" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</Setter.Value>
</Setter>
The problem is that this seems to have no effect
EDIT 3: i realized that i had to use the template that use the style i mentioned above
<ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="Placement" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2"
IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
MinWidth="{Binding ActualWidth, ElementName=Placement}">
<Border x:Name="DropDownBorder"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid RenderOptions.ClearTypeHint="Enabled">
<Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="OpaqueRect"
Fill="{Binding Background, ElementName=DropDownBorder}"
Height="{Binding ActualHeight, ElementName=DropDownBorder}"
Width="{Binding ActualWidth, ElementName=DropDownBorder}" />
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter"
KeyboardNavigation.DirectionalNavigation="Contained"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</ScrollViewer>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<Themes:ListBoxChrome x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" Grid.ColumnSpan="2"
RenderMouseOver="{TemplateBinding IsMouseOver}"
RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" />
<TextBox x:Name="PART_EditableTextBox"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{TemplateBinding Padding}" Style="{StaticResource ComboBoxEditableTextBox}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" >
</TextBox>
<ToggleButton Grid.Column="1"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ComboBoxToggleButton}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="Foreground" Value="Black" />
</Trigger>
<Trigger Property="IsDropDownOpen" Value="true">
<Setter Property="RenderFocused" TargetName="Border" Value="true" />
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="DropDownBorder" Value="95" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter Property="Background" Value="#FFF4F4F4" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</MultiTrigger>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5" />
<Setter Property="Color" TargetName="Shdw" Value="#71000000" />
</Trigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="OpaqueRect"
Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}" />
<Setter Property="Canvas.Left" TargetName="OpaqueRect"
Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
the textbox section is the one that use the style. However when i do Template="{StaticResource ComboBoxEditableTemplate}" in the combobox, i cannot see the selected item anymore, even if it's not null
Updated 08/02
Basically in order to avoid the animation issues you are facing - you will need to add a ScrollViewer as a wrapper to the content-site or selection-box TextBlock. You can either do that by using a custom template selector for your ComboBox so as defined below, or provide a custom item-template with ScollViewer.
Secondly, you can either create a custom ComboBox control in C# to bind the above template-selector/item-template and apply slide-animation on MouseEnterEvent; or implement the same in XAML itself.
For this example - have implemented the control in C#, and provided
a sample usage in XAML.
Code for custom control, and template selector
public class SlidingComboBox : ComboBox
{
public static readonly DependencyProperty SlideForeverProperty = DependencyProperty.Register("SlideForever", typeof(bool), typeof(SlidingComboBox), new FrameworkPropertyMetadata(false));
public bool SlideForever
{
get { return (bool)GetValue(SlideForeverProperty); }
set { SetValue(SlideForeverProperty, value); }
}
protected ContentPresenter _parent;
protected DoubleAnimation _animation;
protected TranslateTransform _translate;
protected Storyboard _storyBoard;
public SlidingComboBox()
{
Loaded += ExComboBox_Loaded;
ClipToBounds = true;
//assign template selector - just to re-template ContentSite / selection box
//uncomment this code - if you want to default-template for popup-items
//ItemTemplateSelector = new SlidingComboBoxItemTemplateSelector();
}
private void ExComboBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExComboBox_Loaded;
//get content-site holder/parent
_parent = this.GetChildOfType<ContentPresenter>();
//setup slide animation
_animation = new DoubleAnimation()
{
From = 0,
RepeatBehavior = SlideForever ? RepeatBehavior.Forever : new RepeatBehavior(1), //repeat only if slide-forever is true
AutoReverse = SlideForever
};
//create storyboard
_storyBoard = new Storyboard();
_storyBoard.Children.Add(_animation);
Storyboard.SetTargetProperty(_animation, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
//get actual textblock that renders the selected value
var textBlock = _parent.GetChildOfType<TextBlock>();
//and translate-transform for animation
textBlock.RenderTransform = _translate = new TranslateTransform();
//start animation only if text-block width is greater than parent
if (_parent.ActualWidth < textBlock.ActualWidth)
{
_animation.Duration = TimeSpan.FromMilliseconds(((int)textBlock.Text?.Length * 100));
_animation.To = _parent.ActualWidth - textBlock.ActualWidth;
_storyBoard.Begin(textBlock);
}
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
//stop animation once mouse pointer is off the control
_storyBoard.Stop();
//reset render state
var textBlock = _parent.GetChildOfType<TextBlock>();
textBlock.RenderTransform = _translate = new TranslateTransform();
base.OnMouseLeave(e);
}
}
public class SlidingComboBoxItemTemplateSelector : DataTemplateSelector
{
DataTemplate _selectedItemTemplate;
public SlidingComboBoxItemTemplateSelector()
{
//create datatemplate with ScrollViewer and TextBlock as child
var textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.TextWrappingProperty, TextWrapping.NoWrap);
textBlock.SetBinding(TextBlock.TextProperty, new Binding("SelectedValue")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ComboBox) }
});
var scrollViewer = new FrameworkElementFactory(typeof(ScrollViewer));
scrollViewer.SetValue(ScrollViewer.CanContentScrollProperty, true);
scrollViewer.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
scrollViewer.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled);
scrollViewer.AppendChild(textBlock);
_selectedItemTemplate = new DataTemplate
{
VisualTree = scrollViewer
};
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
if (comboBoxItem == null)
{
//send back only if template requested for ContentSite, and not for combo-box item(s)
return _selectedItemTemplate;
}
return null;
}
}
/// <summary>
/// VisualTree helper
/// </summary>
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
public static T GetVisualParent<T>(this DependencyObject child) where T : Visual
{
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
Sample usage:
<Window x:Class="MarqueeSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MarqueeSample"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="400">
<Grid Margin="20">
<DataGrid AutoGenerateColumns="False" IsReadOnly="True" Width="225" VerticalAlignment="Center">
<DataGrid.ItemsSource>
<col:Hashtable>
<col:ArrayList x:Key="TestData1">
<sys:String>Tiny</sys:String>
<sys:String>Keep calm and love programming</sys:String>
</col:ArrayList>
<col:ArrayList x:Key="TestData2">
<sys:String>Sample string</sys:String>
<sys:String>Another string to test</sys:String>
</col:ArrayList>
</col:Hashtable>
</DataGrid.ItemsSource>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="100" Binding="{Binding Key}" />
<DataGridTemplateColumn Header="Value" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:SlidingComboBox ItemsSource="{Binding Value}">
<local:SlidingComboBox.ItemTemplate>
<DataTemplate>
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
<TextBlock Text="{Binding}" />
</ScrollViewer>
</DataTemplate>
</local:SlidingComboBox.ItemTemplate>
</local:SlidingComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
You can also use SlideForever property to manipulate RepeatBehaviour.
<local:SlidingComboBox ItemsSource="{Binding Value}" SlideForever="True" />
I've been looking into this and I think I have your solution. You should combine both a RenderTransform and a Storyboard on the ComboBox ContentPresenter (this is what displays the currently selected item)
<ComboBox Grid.Row="1" Name="MyComboBox" Width="200">
<ComboBox.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="0" Y="0" />
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard x:Name="ScrollItem">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Duration="00:00:5" From="0" To="200" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
<DoubleAnimation Duration="00:00:5" BeginTime="00:00:5" From="-200" To="0" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
<ComboBoxItem>
I am combobox value 1
</ComboBoxItem>
<ComboBoxItem>
I am combobox value 2, Hello!
</ComboBoxItem>
</ComboBox>
Having the ComboBox of size 200, scrolling from 0 to 200, then -200 to 0, scrolls the text off the right hand side of the control, and in the left hand side. (You can drop the 2nd DoubleAnimation if you like and set AutoReverse to True to cause the text to bounce back in if you'd rather that. This does not code you around items that are too big for the control, you will need to write some code for the ComboBox so it decides if the currently selected Item is too big, and from code behind (or maybe a custom ComboBox Class) dynamically turn on/off the storyboard.
First iterate through all items of your combobox, check for the width of every items by assigning the text to a label. Then, check width every time, if width of current item gets greater than previous items then change the maximum width.
int DropDownWidth(ComboBox myCombo)
{
int maxWidth = 0;
int temp = 0;
Label label1 = new Label();
foreach (var obj in myCombo.Items)
{
label1.Text = obj.ToString();
temp = label1.PreferredWidth;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
label1.Dispose();
return maxWidth;
}
private void window_loaded(object sender, EventArgs e)
{
comboBox1.DropDownWidth = DropDownWidth(comboBox1);
}
OR
int DropDownWidth(ComboBox myCombo)
{
int maxWidth = 0, temp = 0;
foreach (var obj in myCombo.Items)
{
temp = TextRenderer.MeasureText(obj.ToString(), myCombo.Font).Width;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
return maxWidth;
}
Thanks to those guys got it from here --> Auto-width of ComboBox's content

How to calculator exactly margin value of boder process in range slider?

I create a RangeSlider control:
XAML:
<Style x:Key="RangeSliderRepeatButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent" Height="6" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RangeSliderThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Grid Margin="0,0,0,10">
<Label x:Name="PART_ValueOfSlider" Content="{Binding Value, RelativeSource={RelativeSource AncestorType={x:Type Slider}}}"
Margin="0 -20 0 0"
HorizontalAlignment="Center"
Foreground="Red"/>
<Path x:Name="PART_Rectangle" Fill="Gray">
<Path.Data>
<RectangleGeometry Rect="0,0 10 9" RadiusX="2" RadiusY="2"></RectangleGeometry>
</Path.Data>
<Path.Effect>
<DropShadowEffect ShadowDepth="2" BlurRadius="3" Color="Black" Opacity="0.4" Direction="270" />
</Path.Effect>
</Path>
<Path x:Name="PART_Traingle" Data="M 0 8 L 5 14 L 10 8 Z" Fill="Gray" >
<Path.Effect>
<DropShadowEffect ShadowDepth="1" BlurRadius="0" Color="Black" Opacity="0.4" Direction="270" />
</Path.Effect>
</Path>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PART_Rectangle" Property="Fill" Value="{DynamicResource LabledSliderThumbHoverBrush}" />
<Setter TargetName="PART_Traingle" Property="Fill" Value="{DynamicResource LabledSliderThumbHoverBrush}" />
</Trigger>
<Trigger Property="IsMouseCaptured" Value="True">
<Setter TargetName="PART_Rectangle" Property="Fill" Value="{DynamicResource LabledSliderThumbFocusesBrush}" />
<Setter TargetName="PART_Traingle" Property="Fill" Value="{DynamicResource LabledSliderThumbFocusesBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RangeSlider" TargetType="{x:Type Slider}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="{TemplateBinding MinWidth}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Track Grid.Column="0" x:Name="PART_Track">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource RangeSliderRepeatButton}" Command="Slider.DecreaseLarge" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="PART_Thumb" Style="{StaticResource RangeSliderThumbStyle}"/>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource RangeSliderRepeatButton}" Command="Slider.IncreaseLarge" />
</Track.IncreaseRepeatButton>
</Track>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Grid x:Name="LayoutRoot" Background="#FF878889">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="47*"/>
<ColumnDefinition Width="353*"/>
</Grid.ColumnDefinitions>
<Border VerticalAlignment="Center"
BorderBrush="Black"
Background="Black"
Height="10"
Grid.Column="0"
BorderThickness="1"
Padding="2"
CornerRadius="4" Grid.ColumnSpan="2" Margin="0,5"/>
<Border x:Name="progressBorder"
SnapsToDevicePixels="True"
Background="Blue"
BorderBrush="Blue"
Height="6"
VerticalAlignment="Center" Grid.ColumnSpan="2" Margin="0,7"
/>
<Slider x:Name="LowerSlider"
Minimum="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=Minimum, Mode=TwoWay}"
Maximum="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=Maximum, Mode=TwoWay}"
Value="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=LowerValue, Mode=TwoWay}"
Style="{StaticResource SliderStyle}" Grid.ColumnSpan="2" />
<Slider x:Name="UpperSlider"
Minimum="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=Minimum, Mode=TwoWay}"
Maximum="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=Maximum, Mode=TwoWay}"
Value="{Binding RelativeSource={RelativeSource AncestorType=local:RangeSlider}, Path=UpperValue, Mode=TwoWay}"
Style="{StaticResource SliderStyle}" Grid.Column="1" />
</Grid>
RangeSlider.cs
public partial class RangeSlider : UserControl
{
public RangeSlider()
{
this.InitializeComponent();
this.LayoutUpdated += new EventHandler(RangeSlider_LayoutUpdated);
}
void RangeSlider_LayoutUpdated(object sender, EventArgs e)
{
SetProgressBorder();
}
private void SetProgressBorder()
{
double lowerPoint = (this.ActualWidth * (LowerValue - Minimum)) / (Maximum - Minimum);
double upperPoint = (this.ActualWidth * (UpperValue - Minimum)) / (Maximum - Minimum);
upperPoint = this.ActualWidth - upperPoint;
progressBorder.Margin = new Thickness(lowerPoint, 0, upperPoint, 0);
}
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public double LowerValue
{
get { return (double)GetValue(LowerValueProperty); }
set { SetValue(LowerValueProperty, value); }
}
public double UpperValue
{
get { return (double)GetValue(UpperValueProperty); }
set { SetValue(UpperValueProperty, value); }
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(RangeSlider), new UIPropertyMetadata(0d, new PropertyChangedCallback(PropertyChanged)));
public static readonly DependencyProperty LowerValueProperty =
DependencyProperty.Register("LowerValue", typeof(double), typeof(RangeSlider), new UIPropertyMetadata(10d, new PropertyChangedCallback(PropertyChanged)));
public static readonly DependencyProperty UpperValueProperty =
DependencyProperty.Register("UpperValue", typeof(double), typeof(RangeSlider), new UIPropertyMetadata(90d, new PropertyChangedCallback(PropertyChanged)));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(RangeSlider), new UIPropertyMetadata(100d, new PropertyChangedCallback(PropertyChanged)));
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RangeSlider slider = (RangeSlider)d;
if (e.Property == RangeSlider.LowerValueProperty)
{
slider.UpperSlider.Value = Math.Max(slider.UpperSlider.Value, slider.LowerSlider.Value);
}
else if (e.Property == RangeSlider.UpperValueProperty)
{
slider.LowerSlider.Value = Math.Min(slider.UpperSlider.Value, slider.LowerSlider.Value);
}
slider.SetProgressBorder();
}
}
It work fine when Label x:Name="PART_ValueOfSlider"contain small value.
When Label x:Name="PART_ValueOfSlider" has big value, ProgressBorder display show not as I expected
How to set ProgressBorder work fine? Thanks for help me !!!
I am using one slider Just Like You But Its work fine for me The Xaml code for the Slider
<ControlTemplate x:Key="TimeRangeSliderTemplate" TargetType="{x:Type Slider}">
<StackPanel>
<Border SnapsToDevicePixels="true" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="PART_SelectionRange"/>
<Track x:Name="PART_Track" Grid.Row="1">
<Track.Thumb>
<Thumb x:Name="Thumb">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Rectangle Fill="Red"
Stroke="Black"
StrokeThickness="1"
Width="10"
Height="18"
SnapsToDevicePixels="True"/>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Track.Thumb>
</Track>
</Grid>
</Border>
</StackPanel>
</ControlTemplate>
<Style TargetType="{x:Type sliders:TimeRangeSlider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type sliders:TimeRangeSlider}">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=Part_LowerSlider,Path=Value}" Foreground="Red" VerticalAlignment="Top" Margin="{Binding ElementName=Part_SelectedRect,Path=Margin}"/>
<TextBlock x:Name="PART_RIGHTTHUMPVALUE" Text="{Binding ElementName=Part_UpperSlider,Path=Value}" Foreground="Red" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</StackPanel>
<Grid Grid.Row="1">
<Border BorderThickness="0,1,0,0" BorderBrush="Black" VerticalAlignment="Center" Height="1" Margin="5,0,5,0"/>
<Rectangle x:Name="Part_SelectedRect" Fill="Green" Height="10" HorizontalAlignment="Left"/>
<Slider x:Name="Part_LowerSlider"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding LowerValue}"
Template="{StaticResource TimeRangeSliderTemplate}"
Margin="0,0,0,0"/>
<Slider x:Name="Part_UpperSlider"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding UpperValue}"
Template="{StaticResource TimeRangeSliderTemplate}"
Margin="0,0,0,0"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And here is the Cs file of the Xaml
public class TimeRangeSlider : Control
{
private const string PartName_LowerSlider = "Part_LowerSlider";
private const string PartName_UpperSlider = "Part_UpperSlider";
private const string PartName_SelectedRegion = "Part_SelectedRect";
private const string PART_RIGHTTHUMPVALUE = "PART_RIGHTTHUMPVALUE";
private Slider lowerSlider;
private Slider upperSlider;
Rectangle selectedRect;
TextBlock rightText;
public TimeRangeSlider()
{
this.DefaultStyleKey = typeof(TimeRangeSlider);
this.Loaded += TimeRangeSlider_Loaded;
}
void TimeRangeSlider_Loaded(object sender, RoutedEventArgs e)
{
lowerSlider.ValueChanged += LowerSlider_ValueChanged;
upperSlider.ValueChanged += UpperSlider_ValueChanged;
lowerSlider.Minimum = Minimum;
lowerSlider.Maximum = Maximum;
lowerSlider.Value = LowerValue;
upperSlider.Minimum = Minimum;
upperSlider.Maximum = Maximum;
upperSlider.Value = UpperValue;
SetView();
}
public override void OnApplyTemplate()
{
lowerSlider = this.GetTemplateChild(PartName_LowerSlider) as Slider;
upperSlider = this.GetTemplateChild(PartName_UpperSlider) as Slider;
selectedRect = this.GetTemplateChild(PartName_SelectedRegion) as Rectangle;
rightText = this.GetTemplateChild(PART_RIGHTTHUMPVALUE) as TextBlock;
base.OnApplyTemplate();
}
private void LowerSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
upperSlider.Value = Math.Max(upperSlider.Value, lowerSlider.Value);
SetView();
}
private void UpperSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
lowerSlider.Value = Math.Min(upperSlider.Value, lowerSlider.Value);
SetView();
}
private void SetView()
{
LowerValue = lowerSlider.Value;
UpperValue = upperSlider.Value;
var unit = lowerSlider.ActualWidth / Maximum;
var leftMargin = LowerValue * unit;
if (UpperValue > LowerValue)
{
var width = (UpperValue - LowerValue) * unit;
selectedRect.Width = width;
}
selectedRect.Margin = new Thickness(leftMargin, 0, 0, 0);
rightText.Margin = new Thickness(selectedRect.Width-20, 0, 0, 0);
}
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(TimeRangeSlider), new UIPropertyMetadata(0d));
public double LowerValue
{
get { return (double)GetValue(LowerValueProperty); }
set { SetValue(LowerValueProperty, value); }
}
public static readonly DependencyProperty LowerValueProperty =
DependencyProperty.Register("LowerValue", typeof(double), typeof(TimeRangeSlider), new UIPropertyMetadata(0d));
public double UpperValue
{
get { return (double)GetValue(UpperValueProperty); }
set { SetValue(UpperValueProperty, value); }
}
public static readonly DependencyProperty UpperValueProperty =
DependencyProperty.Register("UpperValue", typeof(double), typeof(TimeRangeSlider), new UIPropertyMetadata(0d));
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(TimeRangeSlider), new UIPropertyMetadata(1d));
}
and the usage of the control
<SliderControl:TimeRangeSlider x:Name="slider"
Margin="10"
LowerValue="30"
UpperValue="70"
Minimum="0"
Maximum="100"
/>
Elements in canvas will not influence for measure. So, use code:
<Canvas>
<Label x:Name="PART_ValueOfSlider" Content="{Binding Value, RelativeSource={RelativeSource AncestorType={x:Type Slider}}}"
Margin="0 0 0 0"
HorizontalAlignment="Center"
Foreground="Red">
<Label.RenderTransform>
<ScaleTransform ScaleY="-1" />
</Label.RenderTransform>
<Label.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleY="-1" />
</TransformGroup>
</Label.LayoutTransform>
</Label>
</Canvas>

How to Reference a Vectored Graphic in Control Binding

All, I have the following resources which defines a custom CheckBox and the images to use for the checked/un-checked states.
<sys:String x:Key="Up">
F1 M 37.8516,35.625L 34.6849,38.7917L 23.6016,50.2708L
23.6016,39.9792L 37.8516,24.9375L 52.1016,39.9792L 52.1016,
50.2708L 41.0182,38.7917L 37.8516,35.625 Z
</sys:String>
<sys:String x:Key="Down">
F1 M 37.8516,39.5833L 52.1016,24.9375L 52.1016,35.2292L
37.8516,50.2708L 23.6016,35.2292L 23.6016,24.9375L 37.8516,39.5833 Z
</sys:String>
<Style x:Key="styleCustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Path x:Name="MyPin" Width="18" Height="18" Stretch="Fill" Fill="#FF000000"
Data="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:CustomCheckBoxClass.IsCheckedOnData)}" />
<ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="MyPin" Property="Data"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:CustomCheckBoxClass.IsCheckedOffData)}" />
<Setter TargetName="MyPin" Property="Fill" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Where this is used as follows
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" Background="Beige">
<CheckBox Height="35"
local:CustomCheckBoxClass.IsCheckedOnData="{StaticResource Up}"
local:CustomCheckBoxClass.IsCheckedOffData="{StaticResource Down}"
Style="{StaticResource styleCustomCheckBox}"
Content="MySolution1" />
<CheckBox Height="35"
local:CustomCheckBoxClass.IsCheckedOnData="{StaticResource Up}"
local:CustomCheckBoxClass.IsCheckedOffData="{StaticResource Down}"
Style="{StaticResource styleCustomCheckBox}"
Content="MySolution2" />
</StackPanel>
I use the following DependencyPropertys to support this
public class CustomCheckBoxClass : DependencyObject
{
public static readonly DependencyProperty IsCheckedOnDataProperty;
public static void SetIsCheckedOnData(DependencyObject DepObject, string value)
{
DepObject.SetValue(IsCheckedOnDataProperty, value);
}
public static string GetIsCheckedOnData(DependencyObject DepObject)
{
return (string)DepObject.GetValue(IsCheckedOnDataProperty);
}
public static readonly DependencyProperty IsCheckedOffDataProperty;
public static void SetIsCheckedOffData(DependencyObject DepObject, string value)
{
DepObject.SetValue(IsCheckedOffDataProperty, value);
}
public static string GetIsCheckedOffData(DependencyObject DepObject)
{
return (string)DepObject.GetValue(IsCheckedOffDataProperty);
}
static CustomCheckBoxClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(string.Empty);
IsCheckedOnDataProperty = DependencyProperty.RegisterAttached(
"IsCheckedOnData", typeof(string), typeof(CustomCheckBoxClass), MyPropertyMetadata);
IsCheckedOffDataProperty = DependencyProperty.RegisterAttached(
"IsCheckedOffData", typeof(string), typeof(CustomCheckBoxClass), MyPropertyMetadata);
}
}
Now I want to know how to extend this so I can use more detailed vector graphics. I have some graphics created using Expression Design and I have exported these as XAML files. The mark up for one of these files is
<DrawingBrush x:Key="Layer_1" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF565656" Geometry="M 107.455,171.715L 107.455,29.3347L 220.225,29.3347L 220.225,171.715L 107.455,171.715 Z "/>
<GeometryDrawing Brush="#FF565656" Geometry="F1 M 152.5,298.345L 152.5,201.955L 175.18,201.955L 175.18,298.345L 152.5,298.345 Z "/>
<GeometryDrawing Brush="#FFFFFFFF" Geometry="M 124.15,172.975L 124.15,48.8647L 165.73,48.8647L 165.73,172.975L 124.15,172.975 Z "/>
<GeometryDrawing Brush="#FF565656" Geometry="F1 M 83.1999,208.885L 83.1999,162.895L 244.48,162.895L 244.48,208.885L 83.1999,208.885 Z "/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
How can I include this in a resource dictionary that can be used directly in a Window/Component? I have attempted to change the DependencyProperty to provide access to DrawingBrush and I have changed the XAML to
<CheckBox Height="35"
local:CustomCheckBoxClass.IsCheckedOnData="{StaticResource Layer_1}"
local:CustomCheckBoxClass.IsCheckedOffData="{StaticResource Down}"
Style="{StaticResource styleCustomCheckBox}"
Content="MySolution1" />
Where I reference the relevant ResourceDictionary, but this has not worked.
Thanks for your time.
Edit. based on #HighCore's comment. Lets say I have the following customised Checkbox
<Style x:Key="MheckBox"
TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Image x:Name="imageCheckBox"
Width="16"
Height="16"
Source="F:\Camus\ResourceStudio\Graphics\Images\UnPinned16.png"/>
<ContentPresenter VerticalAlignment="Center"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="imageCheckBox"
Property="Source"
Value="F:\Camus\ResourceStudio\Graphics\Images\Pinned16.png"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="imageCheckBox"
Property="Source"
Value="F:\Camus\ResourceStudio\Graphics\Images\UnPinned16.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
which works great. How can I change the Value="F:\Camus\ResourceStudio\Graphics\Images\UnPinned16.png" to use the vector graphics I have outlined above?
The fact, that the in Template is used to Path and it assumes a string value in the Data parameter. But now you use DrawingBrush, usually it is used for Rectangle control.
So, we change the type of attached depending properties on the type of DrawingBrush:
public static readonly DependencyProperty IsCheckedOnDataProperty;
public static void SetIsCheckedOnData(DependencyObject DepObject, DrawingBrush value)
{
DepObject.SetValue(IsCheckedOnDataProperty, value);
}
public static DrawingBrush GetIsCheckedOnData(DependencyObject DepObject)
{
return (DrawingBrush)DepObject.GetValue(IsCheckedOnDataProperty);
}
static CustomCheckBoxClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(null);
IsCheckedOnDataProperty = DependencyProperty.RegisterAttached("IsCheckedOnData",
typeof(DrawingBrush),
typeof(CustomCheckBoxClass),
MyPropertyMetadata);
}
In Style, now change the Path on the Rectangle, like this:
<Style x:Key="styleCustomCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<StackPanel Orientation="Horizontal">
<Rectangle Width="16" Height="16"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:CustomCheckBoxClass.IsCheckedOnData)}" />
<ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="MyRectangle" Property="Fill" Value="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=(local:CustomCheckBoxClass.IsCheckedOffData)}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Make a Thumb control sizable using the mouse to drag an edge

I need a thumb control that can be sized using a mouse. When the user hovers the mouse over one of the ends a size cursor should be displayed and when the user clicks and drags the end of the control it will be re-sized.
How can that be achieved?
Here is one I made a while ago, it allows Move and Resize, but you can remove the Move logic and it should work fine (the style is still a bit messy, but it works pretty well)
Its based on ContentControl so you can add any Element inside and Move/Resize on a Canvas, It uses 3 Adorners, one for Resize, one for Move and one to display information (current size)
Here is a full working example if you want to test/use/modify/improve :)
Code:
namespace WpfApplication21
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class ResizeThumb : Thumb
{
public ResizeThumb()
{
DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);
}
private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double deltaVertical, deltaHorizontal;
switch (VerticalAlignment)
{
case VerticalAlignment.Bottom:
deltaVertical = Math.Min(-e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
designerItem.Height -= deltaVertical;
break;
case VerticalAlignment.Top:
deltaVertical = Math.Min(e.VerticalChange, designerItem.ActualHeight - designerItem.MinHeight);
Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + deltaVertical);
designerItem.Height -= deltaVertical;
break;
default:
break;
}
switch (HorizontalAlignment)
{
case HorizontalAlignment.Left:
deltaHorizontal = Math.Min(e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + deltaHorizontal);
designerItem.Width -= deltaHorizontal;
break;
case HorizontalAlignment.Right:
deltaHorizontal = Math.Min(-e.HorizontalChange, designerItem.ActualWidth - designerItem.MinWidth);
designerItem.Width -= deltaHorizontal;
break;
default:
break;
}
}
e.Handled = true;
}
}
public class MoveThumb : Thumb
{
public MoveThumb()
{
DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
}
private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
Control designerItem = this.DataContext as Control;
if (designerItem != null)
{
double left = Canvas.GetLeft(designerItem);
double top = Canvas.GetTop(designerItem);
Canvas.SetLeft(designerItem, left + e.HorizontalChange);
Canvas.SetTop(designerItem, top + e.VerticalChange);
}
}
}
public class SizeAdorner : Adorner
{
private Control chrome;
private VisualCollection visuals;
private ContentControl designerItem;
protected override int VisualChildrenCount
{
get
{
return this.visuals.Count;
}
}
public SizeAdorner(ContentControl designerItem)
: base(designerItem)
{
this.SnapsToDevicePixels = true;
this.designerItem = designerItem;
this.chrome = new Control();
this.chrome.DataContext = designerItem;
this.visuals = new VisualCollection(this);
this.visuals.Add(this.chrome);
}
protected override Visual GetVisualChild(int index)
{
return this.visuals[index];
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
this.chrome.Arrange(new Rect(new Point(0.0, 0.0), arrangeBounds));
return arrangeBounds;
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication21"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<local:MoveThumb Cursor="SizeAll">
<local:MoveThumb.Style>
<Style TargetType="{x:Type local:MoveThumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MoveThumb}">
<Rectangle Fill="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:MoveThumb.Style>
</local:MoveThumb>
<Control x:Name="resizer">
<Control.Style>
<Style TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Grid>
<Grid Opacity="0" Margin="-3">
<local:ResizeThumb Height="3" Cursor="SizeNS" VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
<local:ResizeThumb Width="3" Cursor="SizeWE" VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="3" Cursor="SizeWE" VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
<local:ResizeThumb Height="3" Cursor="SizeNS" VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNWSE" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNESW" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNESW" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
<local:ResizeThumb Width="7" Height="7" Margin="-2" Cursor="SizeNWSE" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Grid>
<Grid IsHitTestVisible="False" Opacity="1" Margin="-3">
<Grid.Resources>
<Style TargetType="{x:Type Ellipse}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Stroke" Value="#FFC8C8C8" />
<Setter Property="StrokeThickness" Value=".5" />
<Setter Property="Width" Value="7" />
<Setter Property="Height" Value="7" />
<Setter Property="Margin" Value="-2" />
<Setter Property="Fill" Value="Silver" />
</Style>
</Grid.Resources>
<Rectangle SnapsToDevicePixels="True" StrokeThickness="1" Margin="1" Stroke="Black" StrokeDashArray="4 4"/>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Ellipse HorizontalAlignment="Right" VerticalAlignment="Top"/>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Ellipse HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Control.Style>
</Control>
<Grid x:Name="sizeInfo" SnapsToDevicePixels="True">
<Path Stroke="Red" StrokeThickness="1" Height="10" VerticalAlignment="Bottom" Margin="-2,0,-2,-15" Stretch="Fill" Data="M0,0 0,10 M 0,5 100,5 M 100,0 100,10"/>
<TextBlock Text="{Binding Width}" Background="White" Padding="3,0,3,0" Foreground="Red" Margin="0,0,0,-18" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
<Path Stroke="Red" StrokeThickness="1" Width="10" HorizontalAlignment="Right" Margin="0,-2,-15,-2" Stretch="Fill" Data="M5,0 5,100 M 0,0 10,0 M 0,100 10,100"/>
<TextBlock Text="{Binding Height}" Background="White" Foreground="Red" Padding="3,0,3,0" Margin="0,0,-18,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90" CenterX="1" CenterY="0.5"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="sizeInfo" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter TargetName="sizeInfo" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Canvas>
<ContentControl Width="183" Height="110" Canvas.Left="166" Canvas.Top="50" />
</Canvas>
</Window>
Result:
With content inside (Button)
Sorry the cursors do not show when using SnipTool

Categories

Resources