WPF Menu binding using HierarchicalDataTemplate is not rendering menu items properly - wpf

I am building an WPF application trying to stick closely to MVVM principles. I am having a problem getting the menu to render correctly. I've tried a few approaches and am getting stuck. It seems like my binding is correct, but I'm not sure about my styles manipulation.
Here's the code I have the problem with. Like I said, it seems the binding is good, and I can even see the correct values for the Header menu items using Snoop, but all I see rendered is empty containers for menu items.
<DockPanel>
<DockPanel.Resources>
<HierarchicalDataTemplate x:Key="TopMenuHDT" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding MenuIcon}" Height="16px" Width="16px" />
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</DockPanel.Resources>
<Menu DockPanel.Dock="Top" Height="auto"
ItemsSource="{Binding TopMenuItems}"
ItemTemplate="{StaticResource TopMenuHDT}"/>
In my main ViewModel:
private ObservableCollection<MenuViewModel> _topMenuItems;
public ObservableCollection<MenuViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value; base.RaisePropertyChanged("TopMenuItems");
}
}
...
public void LoadMainMenu()
{
IList<ViewModels.MenuViewModel> fileMenuItems = PopulateFileMenuEntries();
IList<ViewModels.MenuViewModel> editMenuItems = PopulateEditMenuEntries();
_topMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_File", Children = new ObservableCollection<ViewModels.MenuViewModel>(fileMenuItems) });
_topMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_Edit", Children = new ObservableCollection<ViewModels.MenuViewModel>(editMenuItems) });
private IList<ViewModels.MenuViewModel> PopulateFileMenuEntries()
{
List<ViewModels.MenuViewModel> fileMenuItems = new List<ViewModels.MenuViewModel>();
fileMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_Open", MenuIcon = new BitmapImage(new Uri("pack://application:,,,/Resources/OpenDocument16.png")) , Command = _mainWindowViewModel.OpenCommand });
fileMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
MenuViewModel:
public class MenuViewModel : ObservableObject
{
internal MenuViewModel()
{
IsEnabled = true;
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value; base.RaisePropertyChanged("MenuText");
}
}
private ICommand _command;
public ICommand Command
{
get { return _command; }
set
{
if (_command == value)
return;
_command = value; base.RaisePropertyChanged("Command");
}
}
private BitmapImage _menuIcon;
public BitmapImage MenuIcon
{
get { return _menuIcon; }
set
{
if (_menuIcon == value)
return;
_menuIcon = value; base.RaisePropertyChanged("MenuIcon");
}
}
private ObservableCollection<MenuViewModel> _children;
public ObservableCollection<MenuViewModel> Children
{
get { return _children; }
set
{
_children = value; base.RaisePropertyChanged("Children");
}
}
}
Any help in getting this rendered correctly would be greatly appreciated.
EDIT:
Here's the final solution in case someone comes across this similar issue:
<DockPanel>
<Menu DockPanel.Dock="Top" Height="auto" ItemsSource="{Binding TopMenuItems}" >
<Menu.Resources>
<Image x:Key="MenuIconResource" Height="16" Width="16" Source="{Binding MenuIcon}" x:Shared="False" />
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="InputGestureText" Value="{Binding ShortcutText}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="Icon" Value="{StaticResource MenuIconResource}" />
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>

Try this instead of your DataTemplate
<DockPanel>
<Menu DockPanel.Dock="Top" Height="auto"
ItemsSource="{Binding TopMenuItems}">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding MenuIcon}" Height="16px" Width="16px" />
</Setter.Value>
</Setter>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>
</Menu.Resources>
</Menu>
</DockPanel>

Related

WPF style overwritten when content applied to UserControl

I have a UserContol for a hyperlink. I need to dynamically set the content of the button to a database value.
I have looked at this link [1]:Custom button user control style overwritten when content is set and it seems I have most everything from that post. But I get errors when I try to set the content in the user control. I am not a WPF programmer so any help in small words would be appreciated.
USER CONTROL
`
<UserControl x:Class="DDC.Controls.LinkButton"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="UserControl">
<UserControl.Resources>
<Style x:Key="LinkButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Padding" Value="5,0,0,0" />
<Setter Property="ToolTip" Value="Click to Follow Link" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter
VerticalAlignment="Top">
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextDecorations" Value="Underline" />
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Button Name ="btnLinkAddress"
Style="{DynamicResource LinkButtonStyle}"
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type LinkButton}}}"
Width="Auto"
Height="Auto"
UseLayoutRounding="True"
Click="LinkButton_Click" >
</Button>
</Grid>
</UserControl>
C# Class
public partial class LinkButton : UserControl
{
public new static DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(LinkButton));
public new object Content
{
get { return (string)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
private void LinkButton_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
TextBlock tb = (TextBlock)btn.Content;
Uri uri = new System.Uri(tb.Text);
// Uri uri = new System.Uri(btn.Content.ToString());
Process.Start(new ProcessStartInfo(uri.AbsoluteUri));
e.Handled = true;
}
public LinkButton()
{
InitializeComponent();
}
ERRORS
I get these errors when trying to set the content in the user control
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type LinkButton}}}"`
LinkButton is not supported in a Windows Presentation Foundation (WPF) project.
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.
Rename the property to something else than Content:
public partial class LinkButton : UserControl
{
public static DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(object), typeof(LinkButton));
public object ButtonContent
{
get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
private void LinkButton_Click(object sender, RoutedEventArgs e)
{
...
}
public LinkButton()
{
InitializeComponent();
}
}
...and bind to it like this:
<Button Name ="btnLinkAddress"
Style="{DynamicResource LinkButtonStyle}"
Content="{Binding Path=ButtonContent, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Width="Auto"
Height="Auto"
UseLayoutRounding="True"
Click="LinkButton_Click" >
If you want to set the AncestorType to LinkButton, you should define a namespace mapping:
<Button xmlns:local="clr-namespace:DDC.Controls"
Name ="btnLinkAddress"
Style="{DynamicResource LinkButtonStyle}"
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type local:LinkButton}}}"
Width="Auto"
Height="Auto"
UseLayoutRounding="True"
Click="LinkButton_Click" >

WPF bind image via WCF to TileLayoutControl from ViewModel

My system is working like this.I have a WCF service,which is giving me ImageUrl and I am downloading this Image to my project directory and save it to my file system. Then I want to show this Image in Devexpress TileLayoutControl dynamically. But not showing anything on UI.Also I am not getting error. I tried to give ımage source like this ../MenuPhotos/imagename and like this /TileExample;component/MenuPhotos/imagename
My codes are like this;
XAML;
<dxlc:TileLayoutControl Name="TileList" AllowItemMoving="True" ItemsSource="{Binding Items}" ShowLayerSeparators="True" Padding="40,60,40,10" AllowLayerSizing="True" >
<dxlc:TileLayoutControl.Resources>
<Style TargetType="dxlc:Tile">
<Setter Property="BorderBrush" Value="Blue" />
<!--<Setter Property="Background" Value="{Binding BackgroundColor}" />-->
<Setter Property="Background" Value="{Binding BackgroundColor}" />
<Setter Property="Size" Value="{Binding SizeType}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Image Stretch="UniformToFill" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Source>
<BitmapImage UriSource="/TileExample;component/MenuPhotos/restaurant1.jpg" />
</Image.Source>
</Image>
<Image Stretch="Uniform" Source="{Binding IconImageUrl}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Header" Value="{Binding}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding BackgroundImageUrl}" FontFamily="Segoe UI Light" FontSize="12" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="dxlc:TileLayoutControl.IsFlowBreak" Value="{Binding IsFlowBreak}" />
<Setter Property="dxlc:TileLayoutControl.GroupHeader" Value="Menü" />
<Setter Property="dxwuin:Navigation.NavigateTo" Value="{Binding PageName}" />
<Setter Property="dxwuin:Navigation.NavigationParameter" Value="1" />
</Style>
<Style TargetType="dxlc:TileGroupHeader">
<Setter Property="Foreground" Value="#FFFFFFFF" />
</Style>
</dxlc:TileLayoutControl.Resources>
</dxlc:TileLayoutControl>
And my viewmodel like this;
IEnumerable<ImageItemsModel> items;
public GroupedItemsViewModel()
{
}
public IEnumerable<ImageItemsModel> Items
{
get { return items; }
private set { SetProperty<IEnumerable<ImageItemsModel>>(ref items, value, "Items"); }
}
public void LoadState(object navigationParameter)
{
MyServiceclient = new MyServiceclient();
var items = client.GetAllItems(null);
string remoteUri = "http://example.com/";
foreach (var item in items)
{
if (item.BackgroundImageUrl == null)
{
item.BackgroundImageUrl = "/TileExample;component/Assets/Images/user.jpg";
}
else
{
string remotefilepath = item.BackgroundImageUrl.Replace("../", "").Replace("//", "/");
string remotefilename = remotefilepath.Replace("img/integration/Dashboard/bg/", "");
string myStringWebResource = null;
using (WebClient myWebClient = new WebClient())
{
string path = #"C:\Users\USER\Documents\Visual Studio 2013\Projects\TileExample\TileExample\MenuPhotos\";
string checkpath = #"C:\Users\USER\Documents\Visual Studio 2013\Projects\TileExample\TileExample\MenuPhotos\" + remotefilename;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
if (!File.Exists(checkpath))
{
myStringWebResource = remoteUri + remotefilepath;
try
{
string downloadintopath = Path.Combine(path, remotefilename);
myWebClient.DownloadFile(myStringWebResource, downloadintopath);
}
catch { }
}
}
item.BackgroundImageUrl = item.BackgroundImageUrl.Replace("../../img/integration/Dashboard/bg", "../MemberPhotos");
}
}
Items = items;
}
#region INavigationAware Members
public void NavigatedFrom(NavigationEventArgs e)
{
}
public void NavigatedTo(NavigationEventArgs e)
{
LoadState(e.Parameter);
}
public void NavigatingFrom(NavigatingEventArgs e)
{
}
#endregion
In UI I couldnt saw nothing about my images.If I will include images as a source to visual studio its working.But I will fill to image as a service. So I cannot use this workaround.Thanks.
Hi recently I learn this solution:
In .NET 4.5 dynamic image path should be like this.
<Image Source="pack://application:,,,/TileExample;component/MenuPhotos/restaurant1.jpg"></Image>
This solution working for me.

How to bind ItemsControl in DataGrid behavior

I have this custom vertical scrollbar that is defined in UserControl.Resources, it has a ItemsControl in it called 'ItemsSelected'.
What I would to do is bind it to the DependencyProperty ItemsControlObject in behavior DataGridSelectionChanged. The example binding does not work but shows what I'd like to achieve. What am I missing to bind ItemsSelected?
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=ItemsSelected'.
BindingExpression:(no path);
DataItem=null;
target element is 'DataGridSelectionChanged' (HashCode=43407976);
target property is 'ItemsControlObject' (type 'ItemsControl')
<UserControl>
<UserControl.Resources>
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
...
<!-- BEGIN -->
<ItemsControl Name="ItemsSelected" VerticalAlignment="Stretch"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MarkerCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Gray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- END -->
</Grid>
</ControlTemplate>
<Style TargetType="{x:Type DataGrid}" >
<Style.Resources>
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="18" />
<Setter Property="Template" Value="{StaticResource HorizontalScrollBar}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="18" />
<Setter Property="Height" Value="Auto" />
<Setter Property="Template" Value="{StaticResource VerticalScrollBar}" />
</Trigger>
</Style.Triggers>
</Style>
</Style.Resources>
</Style>
</UserControl.Resources>
<Grid Name="gridUsers" Background="Transparent">
<DockPanel>
<DataGrid Name="GenericDataGrid">
<i:Interaction.Behaviors>
<helpers:DataGridSelectionChanged ItemsControlObject="{Binding ElementName=ItemsSelected}" />
</i:Interaction.Behaviors>
<DataGrid.Columns>
...
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
</UserControl>
[EDIT]
public static class ScrollBarMarkers
{
public static readonly DependencyProperty MarkersSelectedCollectionProperty =
DependencyProperty.RegisterAttached("MarkersSelectedCollection", typeof(ObservableCollection<double>), typeof(ScrollBarMarkers), new PropertyMetadata(null));
public static ObservableCollection<double> GetMarkersSelectedCollection(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(MarkersSelectedCollectionProperty);
}
public static void SetMarkersSelectedCollection(ItemsControl obj, ObservableCollection<double> value)
{
obj.SetValue(MarkersSelectedCollectionProperty, value);
}
}
Implementing things like this stops the need for binding the actual ItemsControl
Here is the binding:
<ItemsControl ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
Here is the class with singleton pattern
public class MyClass : INotifyPropertyChanged
{
public static ObservableCollection<double> m_selectedMarkers = new ObservableCollection<double>();
public ObservableCollection<double> SelectedMarkers
{
get
{
return m_selectedMarkers;
}
set
{
m_selectedMarkers = value;
NotifyPropertyChanged();
}
}
private static MyClass m_Instance;
public static MyClass Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new MyClass();
}
return m_Instance;
}
}
private MyClass()
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

How to display a different value for dropdown list values/selected item in a WPF ComboBox?

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.

WPF error template not showing

I get no binding errors and this code works at another place. I haven't found out yet what I do now differently to the code where it works and it's not that much code.
In UserControl.Resource:
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Below in Xaml too:
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,90,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Height="23" HorizontalAlignment="Left" Margin="22,108,0,0"
VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
The button SAVE in my ViewModel is only activated when the Model.Tags property is longer 10 chars input from the user. The button activation/disable works fine when I enter 10,11 and then back 8 chars. All the property changes are fired.
Model:
namespace TBM.Model
{
public class Document : EntityBase , IDataErrorInfo
{
public int Id { get; set; }
public string DocumentName { get; set; }
public string Tags { get; set; }
public byte[] DocumentData { get; set; }
public int PeriodId { get; set; }
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
if (GetValidationError(property) != null)
return false;
return true;
}
}
static readonly string[] ValidatedProperties = { "Tags", };
private string GetValidationError(string propertyName)
{
if (Array.IndexOf(ValidatedProperties, propertyName) < 0)
return null;
string error = null;
switch (propertyName)
{
case "Tags": error = this.IsTagsEmpty(Tags); break;
default:
Debug.Fail("Unexpected property being validated on Document: " + propertyName);
break;
}
return error;
}
private string IsTagsEmpty(string value)
{
if (value != null && value.Trim().Length >= 10)
return null;
else
return "The keywords must have at least 10 chars!";
}
}
}
ViewModel:
public RelayCommand SaveDocumentCommand
{
get { return _saveDocumentCommand ?? (_saveDocumentCommand =
new RelayCommand(() => SaveDocument(),() => CanSaveDocument())); }
}
private bool CanSaveDocument()
{
return _document.IsValid;
}
//...
What does not work is the ErrorTemplate with the red Ellipse is not showing at all?
UPDATE: Exactly the below code works in a TEST project. But in my productive project It does not find the Resource??? Why the heck this?
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,89,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Style="{StaticResource bla}" Height="23" HorizontalAlignment="Left"
Margin="22,109,0,0" VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"
ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
<UserControl.Resources>
<Style x:Name="bla" TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
I had similar problem. Fight it for hours just to realize that something was wrong with adorner layer.
What I did is put my with controls inside . And that was it. For some reason this decorator layer sometimes is gone. This is certainly true for TabControl (but in my case it was some other reason).
So it should look like this
<AdornerDecorator>
<Grid>
<TextBox .../>
</Grid>
</AdornerDecorator>
Hope this helps!
I have had a similar problem to this, it turned out not to be the template but the validation was not returning what I expected.
The ViewModel have to implement IDataErrorInfo, not the Model. The ViewModel binds to your View as the DataContext, not the Model, so implement the interface in ViewModel and bind to corresponding properties in XAML.

Resources