How to define MenuItems with Icons as Templates in App.xaml - wpf

In my apps, I have a lot context menus. Most of them contain a lot of standard entries such as menu items for the cut, copy and the paste command. Every menu has an attached icon.
How do I define a style for each type of MenuItem which is application wide available? The following xaml is principally what I’m looking for, however the declaration of the image is invalid (if by hazard an instance of the style is used at more than one position at the same time, the image will not be shown).
<Style x:Key="CutMenuItem_Style" TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="Cut"/>
<Setter Property="Header" Value="Cut"/>
<Setter Property="Icon" >
<Setter.Value>
<Image Source="/image/cut_16_16.png" Stretch="None" />
</Setter.Value>
</Setter>
</Style>
How do you do this? Do you declare a ControlTemplate for each menu-type?
Please note: I understand why the above xaml does not work as expected. I’m interested in an alternative and reliable way to do the same as I have intended with the above code.

I'm not sure if this will work or not (haven't tested it), but I'm intrigued by the possibility that you could some something like this:
class MyMenuItem
{
public ICommand Command { get; set; }
public string Header { get; set; }
public ImageSource ImageSource { get; set; }
}
var cutMenuItem = new MyMenuItem()
{
Command = ...,
Header = "Cut",
ImageSource = ...
};
And then use DataTemplates:
<DataTemplate DataType="{x:Type local:MyMenuItem}">
<MenuItem Command="{Binding Command}" Header="{Binding Header}"
Icon="{Binding ImageSource}" />
</MenuItem>
</DataTemplate>
And then your menu:
<Menu ItemsSource="{Binding ListOfMyMenuItems}" />

A possibility is to use a IValueConverter which converts RoutedCommands to Images. The IValueConverter can be used then in the global styles.
The XAML looks as follows:
<Style x:Key="MenuItemBase_Style" TargetType="{x:Type MenuItem}">
<Setter Property="Icon" Value="{Binding Command,Converter={StaticResource CommandImage_ValueConverter},RelativeSource={RelativeSource Mode=Self}}"/>
</Style>
<Style x:Key="CutMenuItem_Style" BasedOn="{StaticResource MenuItemBase_Style}" TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="Cut"/>
</Style>
<Style x:Key="CopyMenuItem_Style" BasedOn="{StaticResource MenuItemBase_Style}" TargetType="{x:Type MenuItem}" >
<Setter Property="Command" Value="Copy"/>
</Style>
<Style x:Key="AnotherMenuItem_Style" ... />
The IValueConverter is simple but can be extended also for other things. I have written it so, that you can provide a Style as a parameter. This is usefull if you want to manipulate the returned Image.
public class CommandImageValueConverter : IValueConverter{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == null) return null;
var cmd = value as RoutedCommand;
if (null != cmd) {
var uri = new Uri("/YourComponent;component/Image/" + cmd.Name + "_16_16.png", UriKind.Relative);
return new Image() { Stretch = Stretch.None ,Source=Load(uri),Style=parameter as Style};
}
throw new NotImplementedException("Conversion from " + value.GetType().Name + " is currently not supported");
}
public static ImageSource Load(Uri uri) {
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = uri;
bi.EndInit();
return bi;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
The IValueConverter must be also be declared once:
<yourNamespace:CommandImageValueConverter x:Key="CommandImage_ValueConverter"/>
Hope this helps someone else.

Related

How to search for a wpf list box item value and highlight the index based on the value?

In my application I would like you highlight an index based on the value. for example:
ArrayList itemsList = new ArrayList();
private void button_Click(object sender, RoutedEventArgs e)
{
itemsList.Add("Coffie");
itemsList.Add("Tea");
itemsList.Add("Orange Juice");
itemsList.Add("Milk");
itemsList.Add("Mango Shake");
itemsList.Add("Iced Tea");
itemsList.Add("Soda");
itemsList.Add("Water");
listBox.ItemsSource = itemsList;
ApplyDataBinding();
}
private void ApplyDataBinding()
{
listBox.ItemsSource = null;
listBox.ItemsSource = itemsList;
}
It does not matter where in the listbox "Orange Juice" is I would like to highlight it based on its value. If the Position change it should be still highlighted. (Not based on the selected index)
If you want to highlight an item based on it's value, then you need to define your own datatemplate for an item and use a converter to provide appropriate brush for the background. Something like that:
<Window.Resources>
<local:TextToBrushConverter x:Key="TextToBrushConverter" />
</Window.Resources>
<Grid>
<ListBox Name="listBox" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Background="{Binding ., Converter={StaticResource TextToBrushConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Converter
class TextToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value as String) == "Orange Juice")
{
return Brushes.Orange;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would recommend that you place the items into a class, think Object Oriented design and then work off of properties (as flags) to give different states.
Then by using Xaml styles to key off those different properties to achieve the affect you are looking for.
Say for example we have an Order class with these properties
public class Order
{
public string CustomerName { get; set; }
public int OrderId { get; set; }
public bool InProgress { get; set; }
}
When an order is marked as in progress (InProgress = true) we want to show red in our list box say for "Alpha" and "Omega" which are in progress:
ListBox Xaml
Here is the Xaml which binds to our data (how you bind is up to you) and shows how to work with Style(s), DataTemplate, and DataTrigger(s) to achieve that:
<ListBox ItemsSource="{StaticResource Orders}"
x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}"
Value="True">
<Setter Property="Foreground"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
Here is the data setup in the pages's resource Xaml to do that, but it could be created in code behind:
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
This should give you enough to start on and create a full featured UI.

how to build dynamically WPF toolbar?

Because ToolBarTray provider no itemsSource i'm using Toolbars as place holders but can't get it done. I have made a user control that is placed MainWindow. I used similar thing to build menu as a base for this and frankly i don't know what I'm doing. I use also the same MenuItemViewModel here. But I can't get anything to the toolbar. The menu works perfect.
<UserControl.Resources>
<ui:ToolbarButtonFilterConverter x:Key="converter" />
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding Path=Command}"/>
<Setter Property="Visibility" Value="{Binding Path=Visibility}"/>
<Setter Property="Content" Value="{Binding Path=BitmapName, Converter={x:Static ui:NativeImageResourceConverter.Default }}"/>
</Style>
<HierarchicalDataTemplate DataType="{x:Type menu:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}" />
</UserControl.Resources>
<ToolBarTray Grid.Row="0" VerticalAlignment="Center" >
<ToolBar Name="Toolbar1" ItemsSource="{Binding Path=ToolbarItems, Converter={StaticResource converter}, ConverterParameter=Toolbar1}"/>
<!-- here more toolbars as placeholders -->
</ToolBarTray>
</UserControl>
The ToolbarViewModel seems to work ok (bound collections) because in convert below the corret item with 6 child items is returned), but xaml is probably faulty. ui:NativeImageResourceConverter is never called for these toolbar buttons that should get created from MenuItemViewModels.
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var items = value as ObservableCollection<MenuItemViewModel>;
if (parameter != null && items != null)
{
if (parameter.ToString() == "Toolbar1")
{
return items.Where(i => i.Header == "Toolbar1");
}
//...
}
return null;
}
Menu items are hiearchical and can contains children in MenuItemViewModel:
public class MenuItemViewModel
{
public Visibility Visibility { get; private set; }
public string BitmapName { get; private set; }
public ICommand Command { get; private set; }
private ObservableCollection<MenuItemViewModel> _menuItems;
public ObservableCollection<MenuItemViewModel> MenuItems;
//...
}

Turn off and on column visibility

I have a DataGrid with many columns and I'd like to provide the users with a drop down that allows them to select which columns they can see. I'm using the .Net 4 WPF DataGrid in a desktop application.
Does anyone know of an easy way to accomplish what I am trying to do.
I do this as follows.
I derive from the grid an add an ICommand called HideShowColumnCommand that takes as its parameter a DataGridColumn (the one I want to hide or show) and hides the column if it is visible, and shows it if it is not.
Then I use a tricky context menu that I attach to the column header that has a tick that shows the column visible/hidden state..
The context menu looks like so
<ContextMenu
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Column.DataGridOwner.Columns}">
<ContextMenu.Resources>
<local:DataGridHeaderVisibilityToBooleanConverter
x:Key="visibilityConverter" />
<BooleanToVisibilityConverter
x:Key="VisibilityOfBool" />
<DataTemplate
DataType="{x:Type DataGridColumn}">
<ContentPresenter
Content="{Binding Path=Header}"
RecognizesAccessKey="True" />
</DataTemplate>
</ContextMenu.Resources>
<ContextMenu.ItemContainerStyle>
<Style
TargetType="MenuItem">
<!--Warning dont change the order of the following two setters
otherwise the command parameter gets set after the command fires,
not much use eh?-->
<Setter
Property="CommandParameter"
Value="{Binding Path=.}" />
<Setter
Property="Command"
Value="{Binding Path=DataGridOwner.HideShowColumnCommand}" />
<Setter
Property="IsChecked"
Value="{Binding Path=Visibility, Converter={StaticResource visibilityConverter}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
The converter like this
public class DataGridHeaderVisibilityToBooleanConverter :IValueConverter{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible) {
return true;
}
else {
return false;
}
}
catch { }
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
#endregion
}

Populating ListBoxItem Background according to a Bool Member Object

I have a class having a Boolean member and I want to populate a Wpf ListBox with a collection of my class.
I want the background of the listboxitem to a different color if my boolean property is false. Is it possible with XAML ? What is the best way to do that ?
class Mi
{
public bool mybool{get;set;}
}
...
List<Mi> mycollection;// the datacontext
You could use a DataTrigger:
<DataTemplate DataType="{x:Type my:Mi}">
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter PropertyName="Background" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding mybool}" Value="True">
<Setter PropertyName="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
<Grid.Style>
... your ListBoxItem contents here ...
</Grid>
</DataTemplate>
Here's a quick general converter for booleans that allows you to specify a value for true and something different for false for properties of any type.
[ValueConversion(typeof(bool), typeof(object))]
public class BooleanValueConverter : IValueConverter
{
public object FalseValue { get; set; }
public object TrueValue { get; set; }
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? this.TrueValue : this.FalseValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return object.Equals(this.TrueValue, value) ? true : false;
}
#endregion
}
Use it like so.
<SolidColorBrush x:Key="TrueBrush" Color="Green" />
<SolidColorBrush x:Key="FalseBrush" Color="Red" />
<local:BooleanValueConverter x:Key="BooleanBackground"
TrueValue="{StaticResource TrueBrush}"
FalseValue="{StaticResource FalseBrush}" />
...
Background="{Binding Path=Some.PropertyPath.Ending.With.A.Boolean,
Converter={StaticResource BooleanBackground}}" />
You could achieve this with a DataTemplateSelector, having two templates with differing backgrounds.
A better way would probably be to bind the the background property to your boolean and use an IValueConverter which would return an appropriate colour.
Background="{Binding Path=mybool, Converter={StaticResource boolToBackgroundConverter}}"

WPF TextBlock Negative Number In Red

I am trying to figure out the best way to create a style/trigger to set foreground to Red, when value is < 0. what is the best way to do this? I'm assuming DataTrigger, but how can I check for negative value, do i have to create my own IValueConverter?
If you are not using an MVVM model (where you may have a ForegroundColor property), then the easiest thing to do is to create a new IValueConverter, binding your background to your value.
In MyWindow.xaml:
<Window ...
xmlns:local="clr-namespace:MyLocalNamespace">
<Window.Resources>
<local:ValueToForegroundColorConverter x:Key="valueToForeground" />
<Window.Resources>
<TextBlock Text="{Binding MyValue}"
Foreground="{Binding MyValue, Converter={StaticResource valueToForeground}}" />
</Window>
ValueToForegroundColorConverter.cs
using System;
using System.Windows.Media;
using System.Windows.Data;
namespace MyLocalNamespace
{
class ValueToForegroundColorConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush = new SolidColorBrush(Colors.Black);
Double doubleValue = 0.0;
Double.TryParse(value.ToString(), out doubleValue);
if (doubleValue < 0)
brush = new SolidColorBrush(Colors.Red);
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
You should have your view specific information in your ViewModel. But you can get rid of the Style specific information in the ViewModel.
Hence create a property in the ViewModel which would return a bool value
public bool IsMyValueNegative { get { return (MyValue < 0); } }
And use it in a DataTrigger so that you can eliminate the ValueConverter and its boxing/unboxing.
<TextBlock Text="{Binding MyValue}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMyValueNegative}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
For Amsakanna's solution I had to add a class name to the Property Setter:
<Setter Property="TextBlock.Foreground" Value="Red" />

Resources