I have the following WPF Style for a custom control
<Style TargetType="{x:Type local:TransportControl}">
<Setter Property="MinorTickBrush" Value="{DynamicResource BlackBrush}"/>
<Setter Property="MajorTickBrush" Value="{DynamicResource BlackBrush}"/>
<Setter Property="IndicatorBrush" Value="{DynamicResource BlckBrush}"/>
<Setter Property="ProgressBorderBrush" Value="{DynamicResource BlackBrush}"/>
<Setter Property="ProgressBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="IndicatorSize" Value="16"/>
<Setter Property="IndicatorBrush" Value="{DynamicResource BlackBrush}"/>
<Setter Property="IndicatorGlow" Value="True"/>
<Setter Property="IndicatorGlowBrush" Value="GhostWhite"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TransportControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{Binding Path=DataContext.IndicatorSize,
RelativeSource={RelativeSource AncestorType={x:Type local:TransportControl}},
Converter={StaticResource ValueToHorizontalPaddingConverter}}"
Margin="4,2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Canvas Name="PART_TimelineCanvas" Grid.Row="0" Height="20" ClipToBounds="False"/>
<Canvas Name="PART_ProgressCanvas" Grid.Row="1" ClipToBounds="False"/>
<Canvas Name="PART_IndicatorCanvas"
Grid.Row="0"
Grid.RowSpan="2"
ClipToBounds="False"
Panel.ZIndex="2"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
with the IValueConverter as
public class ValueToHorizontalPaddingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object format, CultureInfo culture)
{
double padding = System.Convert.ToDouble(value);
return new Thickness(padding, 0, padding, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I am attempting to set the padding of the control so my indicator can be centered correctly. I want the padding of the control to be half the IndicatorSize set in the parent style. Currently, I am just trying to get it to be the IndicatorSize, but the binding I am attempting does not work as expected.
Padding="{Binding Path=DataContext.IndicatorSize,
RelativeSource={RelativeSource AncestorType={x:Type local:TransportControl}},
Converter={StaticResource ValueToHorizontalPaddingConverter}}"
What am I doing wrong?
Thanks for your time.
You can use TemplateBinding to do that:
Padding="{TemplateBinding IndicatorSize, Converter={StaticResource ValueToHorizontalPaddingConverter}}"
Another way of centering the indicator would be to give the border a name, get a reference to it in your custom control's OnApplyTemplate(..) method and set its Padding in C# whenever the IndicatorSize property changes. This way you would not need the binding and converter.
Related
My idea is to use a WPF default button use an image as the opacity mask and have the foreground brush change its color. I want to still have the background however and this is where the issue is, because no matter how I design the style the background always disappears. Either this isn’t possible because the opacity Mask cannot change its target and just acts on the entire control or I am missing something.
Here is the style i have. I am trying to keep it fairly basic.
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Background" Value="{DynamicResource Mono_Darkest}"/>
<Setter Property="Foreground" Value="{DynamicResource Mono_Light}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button_BorderBrush_Normal}"/>
<Setter Property="BorderThickness" Value="{StaticResource Button_BorderThickness_Normal}"/>
<Setter Property="FontFamily" Value="{StaticResource Button_FontFamily_Normal}"/>
<Setter Property="FontWeight" Value="{StaticResource Button_FontWeight_Normal}"/>
<Setter Property="FontSize" Value="{StaticResource Button_FontSize_Normal}"/>
<Setter Property="HorizontalContentAlignment" Value="{StaticResource Button_HorizontalContentAlignment_Normal}"/>
<Setter Property="VerticalContentAlignment" Value="{StaticResource Button_VerticalContentAlignment_Normal}"/>
<Setter Property="Padding" Value="{StaticResource Button_Padding_Normal}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border_Part"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Label OpacityMask="{TemplateBinding OpacityMask}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}" FontWeight="{TemplateBinding FontWeight}">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OpacityMask, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}, Converter={StaticResource isNull}}"
Value="false">
<Setter Property="Background"
Value="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
What I have is simply nesting a label inside the border. What I want is the template (Button) control's opacity property to only affect the label's opacity property. But that doesn’t seem to be happening. Instead it seems to affect everything like the default button style would.
I have tried Setting OverridesDefaultStyle to True but that has not changed anything.
I know I can already do this other ways. But my goal is to just make a quick button that can use either text or images as a foreground. With the image color coming from the foreground brush. If I cannot get an answer here I am most likely just going to use an Attached Property and bind the labels opacity mask to that instead, but again I would rather just do it entirely in this style.
Any advice is welcome. Thank you.
For Opacity I use a Converter. With this I can bind a SolidColorBrush to Foreground or Background. The Converter gets a Parameter with the target Opacity and returns the opac color.
Converter:
public class ColorToOpacityConverter : IValueConverter
{
// Takes a SolidColorBrush as value and double as parameter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is SolidColorBrush)) return value;
SolidColorBrush scb = new SolidColorBrush(((SolidColorBrush) value).Color);
scb.Opacity = parameter as double? ?? 0.5;
return scb;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml:
<Style.Resources>
<namespace:ColorToOpacityConverter x:Key="ColorToOpacityConverter" />
<system:Double x:Key="TargetOpacity">0.3</system:Double>
</Style.Resources>
<Label Foreground="{Binding Path=Foreground, StaticResource={StaticResource TemplatedParent}, Converter={StaticResource ColorToOpacityConverter}, ConverterParameter={StaticResource TargetOpacity}"
A second approach for bound opacities (not as StaticResource) I use a MultiValueConverter
For bound opacities:
public class ColorToDynOpacityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(v => v == DependencyProperty.UnsetValue)) return new SolidColorBrush();
if (!(values[0] is SolidColorBrush) || !(values[1] is double)) {
//Fake for DesignMode-Display
if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return new SolidColorBrush(Colors.Aqua);
throw new ArgumentException("expect values[0] as SolidColorBrush and values[1] as double.");
}
SolidColorBrush scb = new SolidColorBrush(((SolidColorBrush) values[0]).Color);
scb.Opacity = (double) values[1];
return scb;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml:
<namespace:ColorToDynOpacityConverter x:Key="ColorToDynOpacityConverter" />
<Label>
<Label.Foreground>
<MultiBinding Converter="{StaticResource ColorToDynOpacityConverter}">
<Binding Path="Foreground" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="Opacity" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</Label.Foreground>
</Label>
Hope that helps and working for you.
I hope I can explain this clearly....
We are binding our DataGrid to a collection that comes from some datasource.
Attributes for each column are described in a different collection, so we create the columns at runtime and set properties on the column (readonly, for example) based on values in the attributes collection.
A new requirement is a 'required' attribute. For columns that are required, I'd like to bind a converter that sets the DataGridCell's background color based on the value. (The simplest case of converter would be some color if the cell were empty, and white if the user entered a value. I'm sure more sophisticated validation will be expected in the future.)
I think it can be done in something like what I'm tinkering with now :
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}">
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
(Still need to add the converter somewhere....)
Or will what I want to do have to be done in code-behind? Any pointers would be greatly appreciated...
Here is one way of doing it. IDK if it's the best way, but it works and it's been a couple hours since you asked so....
Your DataGridCell is filled with the border/textbox so I'm assuming you want to change the textbox's background color since you won't see the DataGridCell's background.
Since you mentioned there could be more complex scenarios in the future, I used a multibinding with a converter and passed in the textboxes datacontext (by using <Binding />) and it's text value.
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}">
<TextBox.Resources>
<local:ValidationBGConverter x:Key="ValidationBGConverter" />
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource ValidationBGConverter}">
<Binding />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content.Text" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And here is the converter:
public class ValidationBGConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2)
return Brushes.Black;
var datacontext = values[0] as ViewData; // Or whatever the textbox's datacontext object is
if (datacontext != null) // If null, probably the new item row
{
var txt = values[1] as string; // Textbox text
if (string.IsNullOrWhiteSpace(txt))
return Brushes.Red;
if (txt.Length < 3)
return Brushes.Pink;
if (txt.Length > 5)
return new LinearGradientBrush(Colors.White, Colors.Blue, 90.0);
}
return Brushes.White;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And, a screenshot:
I have a WPF combobox bound to a list of items with long descriptions.
The type bound to the ComboBox has both short and long description as properties. Currently, I am binding to the full description.
comboBox.DisplayMemberPath = "FullDescription";
How to ensure that when the item is selected and displayed as a single item in the combobox, it will be displayed as a value of the ShortDescription property while the dropdown will display FullDescription?
Update 2011-11-14
I recently came upon the same requirement again and I wasn't very happy with the solution I posted below. Here is a nicer way to get the same behavior without re-templating the ComboBoxItem. It uses a DataTemplateSelector
First, specify the regular DataTemplate, the dropdown DataTemplate and the ComboBoxItemTemplateSelector in the resources for the ComboBox. Then reference the ComboBoxItemTemplateSelector as a DynamicResource for ItemTemplateSelector
<ComboBox ...
ItemTemplateSelector="{DynamicResource itemTemplateSelector}">
<ComboBox.Resources>
<DataTemplate x:Key="selectedTemplate">
<TextBlock Text="{Binding Path=ShortDescription}"/>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<TextBlock Text="{Binding Path=FullDescription}"/>
</DataTemplate>
<local:ComboBoxItemTemplateSelector
x:Key="itemTemplateSelector"
SelectedTemplate="{StaticResource selectedTemplate}"
DropDownTemplate="{StaticResource dropDownTemplate}"/>
</ComboBox.Resources>
</ComboBox>
ComboBoxItemTemplateSelector checks if the container is the child of a ComboBoxItem, if it is, then we are dealing with a dropdown item, otherwise it is the item in the ComboBox.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate DropDownTemplate
{
get;
set;
}
public DataTemplate SelectedTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ComboBoxItem comboBoxItem = VisualTreeHelpers.GetVisualParent<ComboBoxItem>(container);
if (comboBoxItem != null)
{
return DropDownTemplate;
}
return SelectedTemplate;
}
}
GetVisualParent
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
Old solution, requires re-templating of ComboBoxItem
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<ControlTemplate x:Key="FullDescriptionTemplate" TargetType="ComboBoxItem">
<Border Name="Border" Padding="2" SnapsToDevicePixels="true">
<StackPanel>
<TextBlock Text="{Binding Path=FullDescription}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="Background" Value="{StaticResource SelectedBackgroundBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ComboBox Name="c_comboBox" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ShortDescription}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template" Value="{StaticResource FullDescriptionTemplate}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
This results in the following behavior
It doesn't seem to work for me now, but this one does:
public class ComboBoxItemTemplateSelector : DataTemplateSelector {
public DataTemplate SelectedTemplate { get; set; }
public DataTemplate DropDownTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var presenter = (ContentPresenter)container;
return (presenter.TemplatedParent is ComboBox) ? SelectedTemplate : DropDownTemplate;
}
}
I modified this custom rounded WPF ComboBox to display a different value from the item selected as well as change the color for each item.
Custom ComboBox
First you need to create the structure:
//Structure
public class COMBOITEM
{
string _ITEM_NAME;
string _ITEM_SHORT_NAME;
Brush _ITEM_COLOR;
public string ITEM_NAME
{
get { return _ITEM_NAME; }
set { _ITEM_NAME = value; }
}
public string ITEM_SHORT_NAME
{
get { return _ITEM_SHORT_NAME; }
set { _ITEM_SHORT_NAME = value; }
}
public Brush ITEM_COLOR
{
get { return _ITEM_COLOR; }
set { _ITEM_COLOR = value; }
}
}
Initialize the structure, fill it with data and bind to ComboBox:
private void Load_Data()
{
Brush Normal_Blue = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF1F4E79"));
//Load first entry
ObservableCollection<COMBOITEM> _Line_Data = new ObservableCollection<COMBOITEM>();
_Line_Data.Add(new COMBOITEM() { ITEM_NAME = "Line Number 1", ITEM_SHORT_NAME = "LN 1", ITEM_COLOR = Normal_Blue });
//Load Test Data
for (int i = 2; i < 10; i++)
{
_Line_Data.Add(new COMBOITEM()
{
ITEM_NAME = "Line Number " + i.ToString(),
ITEM_SHORT_NAME = "LN " + i.ToString(),
ITEM_COLOR = (i % 2 == 0) ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red) //This just changes color
});
}
//Bind data to combobox
cb_Test.ItemsSource = _Line_Data;
}
Now place the ComboBox in your design. To use it as a normal ComboBox, remove DisplayMemberPath and rename "ColorComboBoxItem" to "CustomComboBoxItem":
<ComboBox x:Name="cb_Test" FontSize="36" Padding="1,0" MinWidth="100" MaxWidth="400" Margin="5,53,10,207" FontFamily="Calibri" Background="#FFBFBFBF" Foreground="#FF1F4E79" BorderBrush="#FF1F4E79" VerticalContentAlignment="Center" TabIndex="5" IsSynchronizedWithCurrentItem="False"
Style="{DynamicResource RoundedComboBox}"
ItemContainerStyle="{DynamicResource ColorComboBoxItem}"
DisplayMemberPath="ITEM_SHORT_NAME" />
Now add the following styles/template to App.xaml Application.Resources:
<!-- Rounded ComboBox Button -->
<Style x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<Border
x:Name="Border"
Grid.ColumnSpan="2"
CornerRadius="8"
Background="{TemplateBinding Background}"
BorderBrush="#FF1F4E79"
BorderThickness="2"
/>
<Path
x:Name="Arrow"
Grid.Column="1"
Fill="{TemplateBinding Foreground}"
Stroke="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
<Border x:Name="PART_ContentHost" Focusable="True" />
</ControlTemplate>
<!-- ComboBox Template -->
<Style x:Key="RoundedComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="Foreground" Value="#333" />
<Setter Property="BorderBrush" Value="Gray" />
<Setter Property="Background" Value="White" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="FontSize" Value="13" />
<Setter Property="MinWidth" Value="150"/>
<Setter Property="MinHeight" Value="35"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton
Cursor="Hand"
Name="ToggleButton"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Style="{StaticResource ComboBoxToggleButton}"
Grid.Column="2"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<ContentPresenter
Name="ContentSite"
IsHitTestVisible="False"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Margin="10,3,30,3"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBox x:Name="PART_EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="3,3,23,3"
Focusable="True"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}"/>
<Popup
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid
Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border
CornerRadius="10"
x:Name="DropDownBorder"
Background="#FFBFBFBF"
BorderThickness="2"
BorderBrush="#FF1F4E79"
/>
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false"/>
<Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
</Style.Triggers>
</Style>
<!--This style uses the normal items.add function-->
<Style x:Key="CustomComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="30" />
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border
Name="Border"
Padding="5"
Margin="2"
BorderThickness="2,0,0,0"
CornerRadius="0"
Background="Transparent"
BorderBrush="Transparent">
<TextBlock TextAlignment="Left">
<ContentPresenter />
</TextBlock>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
<Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--This style uses the structure to fill items and set the item color-->
<Style x:Key="ColorComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="30" />
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Foreground" Value="{Binding ITEM_COLOR}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border
Name="Border"
Padding="5"
Margin="2"
BorderThickness="2,0,0,0"
CornerRadius="0"
Background="Transparent"
BorderBrush="Transparent">
<TextBlock Text="{Binding ITEM_NAME}" TextAlignment="Left">
</TextBlock>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
<Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope this helps..
This solution is for WPF + MVVM.
Some of the other solutions work, and some of them do not. The problem with some other solutions are that if they do not work, it's sometimes difficult to debug why it is not working, especially if one is not experienced with WPF.
In my opinion, it's preferable to use strings for the bindings, and convert to an enum in C# which means everything is easier to troubleshoot.
You might need to use ReSharper, it will auto-suggest any missing namespaces.
Create an enum with description attributes:
public enum EnumSelectedView
{
[Description("Drop Down 1")]
DropDown1 = 0,
[Description("Drop Down 2")]
DropDown2 = 1,
}
And a ComboBox:
<ComboBox HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="130"
ItemsSource="{Binding AvailableSelectedViews, Mode=OneWay}"
SelectedItem="{Binding SelectedView, Mode=TwoWay, Converter={StaticResource enumToDescriptionConverter}}"
</ComboBox>
The converter in XAML needs to be pointed at the C# class. If you are using a UserControl or a Window, it would be UserControl.Resources or Window.Resources.
<DataTemplate.Resources>
<converters:EnumToDescriptionConverter x:Key="enumToDescriptionConverter" />
</DataTemplate.Resources>
Add some extension methods and a converter anywhere in your project:
using System;
namespace CMCMarkets.Phantom.CoreUI.Converters
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
public class EnumToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value is Enum) == false) throw new ArgumentException("Error: value is not an enum.");
return ((Enum)value)?.GetDescriptionAttribute();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value is string) == false)
{
throw new ArgumentException("Error: Value is not a string");
}
foreach (var item in Enum.GetValues(targetType))
{
var asString = (item as Enum).GetDescriptionAttribute();
if (asString == (string)value)
{
return item;
}
}
throw new ArgumentException("Error: Unable to match string to enum description.");
}
}
public static class EnumExtensions
{
/// <summary>
/// For a single enum entry, return the [Description("")] attribute.
/// </summary>
public static string GetDescriptionAttribute(this Enum enumObj)
{
FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
object[] attribArray = fieldInfo.GetCustomAttributes(false);
if (attribArray.Length == 0)
{
return enumObj.ToString();
}
else
{
DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
return attrib?.Description;
}
}
/// <summary>
/// For an enum type, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example: List<string> descriptions = EnumExtensions.GetDescriptionAttributeList<MyEnumType>();
*/
public static List<string> GetDescriptionAttributeList<T>()
{
return typeof(T).GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
/// <summary>
/// For an enum instance, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example:
*
* List<string> descriptions = typeof(CryptoExchangePricingOrGraphView).GetDescriptionAttributeList();
*/
public static List<string> GetDescriptionAttributeList(this Type type)
{
return type.GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
/// <summary>
/// For an enum instance, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example:
*
* MyEnumType x;
* List<string> descriptions = x.GetDescriptionAttributeList();
*/
public static List<string> GetDescriptionAttributeList(this Enum thisEnum)
{
return thisEnum.GetType().GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
}
}
In your ViewModel:
public IReadOnlyList<string> AvailableSelectedViews { get; }
And in the constructor:
this.AvailableSelectedViews = typeof(EnumSelectedView).GetDescriptionAttributeList();
The selected item will be bound to this. It uses the converter to go from the string in the combobox straight to the enum. You could also do the conversion inside the property updater by using the extension methods above.
public EnumSelectedView SelectedView { get; set; }
Another option I have found is to place a textbox over the combobox text area. Size and align it so that it lays perfectly over it then use a sub similar to this:
Private Sub ComboBox*_Change()
Dim T As String
T = Left(ComboBox*.Text, 1)
TextBox*.Value = T
End Sub
(replace the * with the relevant numbers)
the result is that when selected the dropdown will display the list as usual but the textbox lying over it will only show its first character.
Hope this helps.
The accepted solution only works if IsEditable is false.
If IsEditable is true, i.e., if the control is a "real" combo box in the sense of combining a list and a free-input text box, there is a really simple solution:
<ComboBox ...
DisplayMemberPath="PropertyToUseForList"
TextSearch.TextPath="PropertyToUseForTextBox" />
Note that this works even if IsTextSearchEnable is false.
Well it's not really very dynamic, at least it won't change at runtime.
The idea is I have buttons and each one has a unique image ( icon 32x32 ). The buttons all share a style where I mess with the ControlTemplate. So each image also has 2 colors one normal and another when I mouse over.
I noticed that when I declare the source path for the images is that they are almost all the same so I though DRY ( Don't Repeat Yourself ). What if I could use the button Name or some other property as part of the source path ( i.e. the name of the image file ). That would be good programming.
Problem is I'm new to XAML, WPF and perhaps programming all together so I'm not sure how to do that. I guess this would need code behind or a converter of some sort ( guess I'll read about converters a bit more ). Here is a bit of code ( this doesn't work but it gives you the general idea ( hopefully )):
<Style x:Key="ButtonPanelBigButton" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="ButtonBorder"
Height="78"
MaxWidth="70"
MinWidth="50"
BorderThickness="0.5"
BorderBrush="Transparent"
CornerRadius="8" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Here I wan't to put in the Name property of the button because there is a picture there to match -->
<Image x:Name="ButtonIcon" Source="..\Images\Icons\32x32\Blue\{Binding Name}.png"
Margin="4"
Height="32"
Width="32"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<TextBlock Grid.Row="1"
Padding="5,2,5,2"
TextWrapping="Wrap"
Style="{StaticResource MenuText}"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter ContentSource="Content" />
</TextBlock>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter TargetName="ButtonIcon" Property="Source" Value="..\Images\Icons\32x32\Green\user.png" /> <!-- Same Here -->
<Setter TargetName="ButtonBorder" Property="BorderBrush" Value="{StaticResource SecondColorBrush}" />
<Setter TargetName="ButtonBorder" Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" Opacity="0.5">
<GradientStop Color="{StaticResource MainColor}" Offset="1" />
<GradientStop Color="{StaticResource SecondColor}" Offset="0.5" />
<GradientStop Color="{StaticResource MainColor}" Offset="0" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Hopefully you get where I'm going with this and someone might be able to help me so my code is nice and DRY ( now I'M repeating myself!!! ).
You're right: The easy way to solve this is to use a converter.
The Source property takes an ImageSource, so you'll need to load the bitmap yourself in your converter.
The converter is used like this:
<Image Source="{Binding Name,
RelativeSource={RelativeSource TemplatedParent},
Converter={x:Static local:ImageSourceLoader.Instance},
ConverterParameter=..\Images\Icons\32x32\Blue\{0}.png}" />
And implemented like this:
public class ImageSourceLoader : IValueConverter
{
public static ImageSourceLoader Instance = new ImageSourceLoader();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var path = string.Format((string)parameter, value.ToString());
return BitmapFrame.Create(new Uri(path, UriKind.RelativeOrAbsolute));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that this simple solution can't handle relative Uris because the BaseUri property of the Image is unavailable to the converter. If you want to use relative Uris, you can do this by binding an attached property and using StringFormat:
<Image local:ImageHelper.SourcePath="{Binding Name,
RelativeSource={RelativeSource TemplatedParent},
StringFormat=..\Images\Icons\32x32\Blue\{0}.png}" />
And the attached property's PropertyChangedCallback handles loads the image after combining the BaseUri with the formatted Uri string:
public class ImageHelper : DependencyObject
{
public static string GetSourcePath(DependencyObject obj) { return (string)obj.GetValue(SourcePathProperty); }
public static void SetSourcePath(DependencyObject obj, string value) { obj.SetValue(SourcePathProperty, value); }
public static readonly DependencyProperty SourcePathProperty = DependencyProperty.RegisterAttached("SourcePath", typeof(string), typeof(ImageHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((Image)obj).Source =
BitmapFrame.Create(new Uri(((IUriContext)obj).BaseUri, (string)e.NewValue));
}
});
}
You can use the Tag Property and set the full path of the image in Tag and then use it.
the following my code to be more clear
*<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#373737" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="12" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border CornerRadius="7" Background="{TemplateBinding Background}" FlowDirection="RightToLeft">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="imgDistance" Source="{Binding Tag,RelativeSource={RelativeSource TemplatedParent}}"
Width="35" Height="35" HorizontalAlignment="Left" Margin="42,7,0,8" Grid.Column="0"/>*
and then I used it in the button as following
<Button x:Name="btnDistance" Height="50" VerticalAlignment="Top" Margin="0,4,0,0" Curs
or="Hand" Click="btnDistance_Click" Tag="/Images/distance.png">
I have a button control style and I want to change the padding from whatever the data-bound version is to adjust for a glyph that needs a 2 pixel offset. I'll use SimpleButton from SimpleStyles.xaml as an example (... shows where the trigger code was removed for conciseness):
<Style x:Key="SimpleButton" TargetType="{x:Type Button}" BasedOn="{x:Null}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
<Setter Property="Background" Value="{DynamicResource NormalBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource NormalBorderBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<!-- We use Grid as a root because it is easy to add more elements to customize the button -->
<Grid x:Name="Grid">
<Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"/>
<!-- Content Presenter is where the text content etc is placed by the control. The bindings are useful so that the control can be parameterized without editing the template -->
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Grid>
...
</Setter.Value>
</Setter>
</Style>
What I want to do is add some extra margin where Padding="{TemplateBinding Padding}". Something like Padding="{TemplateBinding Padding} + 2,0,0,0".
Is there a XAML syntax to that? If not, is there a best approach when doing this in code (Decorator?) ?
Currently XAML does not parse expressions in Binding syntax, etc. However, you can use an IValueConverter or IMultiValueConverter to help yourself out:
XAML:
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="Grid">
<Grid.Resources>
<local:ThicknessAdditionConverter x:Key="AdditiveThickness" />
</Grid.Resources>
<Border x:Name="Border">
<Border.Padding>
<Binding Path="Padding" RelativeSource="{RelativeSource TemplatedParent}"
Converter="{StaticResource AdditiveThickness}">
<Binding.ConverterParameter>
<Thickness>2,0,0,0</Thickness>
</Binding.ConverterParameter>
</Binding>
</Border.Padding>
</Border>
...
</Setter.Value>
IValueConverter code behind:
public class ThicknessAdditionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return new Thickness(0, 0, 0, 0);
if (!(value is Thickness)) throw new ArgumentException("Value not a thickness", "value");
if (!(parameter is Thickness)) throw new ArgumentException("Parameter not a thickness", "parameter");
var thickness = new Thickness(0, 0, 0, 0);
var t1 = (Thickness)value;
var t2 = (Thickness)parameter;
thickness.Left = t1.Left + t2.Left;
thickness.Top = t1.Top + t2.Top;
thickness.Right = t1.Right + t2.Right;
thickness.Bottom = t1.Bottom + t2.Bottom;
return thickness;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There is a product available at Blendables.com called Eval Binding and Simple Binding does this now. (The product is not free) Check out the whitepaper here
For example for the bellow XAML code you need a converter to do the operation.
<Ellipse Fill="Blue" Height="50"
Width="{Binding RelativeSource={RelativeSource Self},
Path=Height, Converter={StaticResource MyConverter}}" />
But with EvalBinding you can do like bellow
<Ellipse Fill="Blue" Height="50"
Width="{blendables:EvalBinding [{Self}.Height]/2}" />
No, not in this version of XAML - use a Value Converter to do your math.
Check out the ExpressionConverter in this library.
You can do some simple math by taking advantage of transforms.
Check out this trick that Charles Petzold came up with a long time ago:
http://www.charlespetzold.com/blog/2006/04/060223.html
Unfortunately, it doesn't seem to help your particular scenario ... since you want only to change Left property of the Thickness type for the Padding ... and that is not a dependency property that you can bind to alone.
However, I felt compelled to add this answer in the case it helps others who find their way here via Google or another search engine.
Check out the MathConverter: http://rachel53461.wordpress.com/2011/08/20/the-math-converter/
There you can send in an expression as the converter-parameter, where #VALUE is the value you are binding to:
ConverterParameter=((#VALUE-15)*.2)