I'm Building a Win 10 UWP app. I have a ListView as per the below xaml, which is the content of a Button.Flyout. It is essentially the same as the standard ListView control, but provides a way for me to bind to the IsSelected property of the particular item. I've added the implementation for completeness. When MinWindowWitdth is 720, the placement mode is changed to Right. Whilst in this state, the ListViewItem stretches to fill the ListView. When not in this state (default), it is in Placement.Full, and the ListViewItem doesn't stretch to the entire ListView, which does seem to stretch to fill of the Flyout, as the VerticalScrollBar is visible against the right hand edge of the Flyout.
From what I could investigate, setting the ListView.ItemContainerStyle with HorizontalContentAlignment.Stretch should have achieved the outcome, however I am left with content that only stretches about half way across and I can't figure out why.
I notice that he CommandBar is included in the scrollable content in the full screen Flyout, however, when in Placement.Right it is not (which is the intended design). So it appears that Full is causing the Flyout to create a ScrollViewer and disregard the ListView.HorizontalAlignment.Stretch property. Is there a way to get around this?
xaml;
<Button.Flyout>
<Flyout x:Name="RulesListFlyout" Placement="Full">
<RelativePanel HorizontalAlignment="Stretch">
<commontools:ListViewWithSelectedItems x:Name="RulesListForInclusion" ItemsSource="{Binding AllRulesForInclusion}" SelectionMode="Multiple" IsMultiSelectCheckBoxEnabled="True"
RelativePanel.Above="RulesCommandB" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="ListViewItem">
<RelativePanel Margin="12,0" HorizontalAlignment="Stretch">
<TextBlock x:Name="Title" Text="{Binding StoredDataObject.Title}" RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignTopWithPanel="True" RelativePanel.AlignRightWithPanel="True"/>
<TextBox x:Name="Variable" Text="{Binding InputVariable, Mode=TwoWay}" Width="50" AcceptsReturn="False" RelativePanel.Below="Title"
Visibility="{Binding ShowVariableBox, Converter={StaticResource TrueToVis}, Mode=TwoWay}"/>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</commontools:ListViewWithSelectedItems>
<CommandBar x:Name="RulesCommandB" Grid.Row="1" RelativePanel.AlignBottomWithPanel="True" RelativePanel.AlignLeftWithPanel="True" RelativePanel.AlignRightWithPanel="True">
<AppBarButton Icon="Accept" Label="Accept" Command="{Binding CommandSetIncludedRules}" Click="SetSpecialRuleFlyoutButton_Click"/>
<AppBarButton Icon="Cancel" Label="Cancel" Command="{Binding CommandResetIncludedRules}" Click="SetSpecialRuleFlyoutButton_Click"/>
</CommandBar>
</RelativePanel>
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="100"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</Flyout.FlyoutPresenterStyle>
</Flyout>
</Button.Flyout>
ListView Implementation;
public class ListViewWithSelectedItems : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
ListViewItem listItem = element as ListViewItem;
Binding binding = new Binding();
binding.Mode = BindingMode.TwoWay;
binding.Source = item;
binding.Path = new PropertyPath("IsCheckedInList");
listItem.SetBinding(ListViewItem.IsSelectedProperty, binding);
}
}
Related
I have a listbox that loads it's items with Foreground color set to red. What I'd like to do is: upon selecting an item with the mouse, change the foreground color of SelectedItem to black, but make the change persistent so that after deselecting the item, color remains black. Incidentally I want to implement this as a way of showing 'read items' to the user.
Essentially I want something like an implementation of the common property trigger like the code below, but not have the style revert after deselection. I've played around with event triggers as well without much luck.
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Foreground" Value="Black" /> //make this persist after deselection
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
Thanks in advance!
You could animate the Foreground property:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(ListBoxItem.Foreground).(SolidColorBrush.Color)"
To="Black" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The downside of this simple approach is that the information is not stored somewhere. This is pure visualization without any data backing. In order to persist the information, so that restarting the application shows the same previous state, you should introduce a dedicated property to your data model e.g IsMarkedAsRead.
Depending on your requirements, you can override the ListBoxItem.Template and bind ToggleButton.IsChecked to IsMarkedAsRead or use a Button which uses a ICommand to set the IsMarkedAsRead property. There are many solutions e.g. implementing an Attached Behavior.
The following examples overrides the ListBoxItem.Template to turn the ListBoxItem into a Button. Now when the item is clicked the IsMarkedAsRead property of the data model is set to true:
Data model
(See Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern for an implementation example of the RelayCommand.)
public class Notification : INotifyPropertyChanged
{
public string Text { get; set; }
public ICommand MarkAsReadCommand => new RelayCommand(() => this.IsMarkedAsRead = true);
public ICommand MarkAsUnreadCommand => new RelayCommand(() => this.IsMarkedAsRead = false);
private bool isMarkedAsRead;
public bool IsMarkedAsRead
{
get => this.isMarkedAsRead;
set
{
this.isMarkedAsRead = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ListBox
<ListBox ItemsSource="{Binding Notifications}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="{TemplateBinding Background}">
<Button x:Name="ContentPresenter"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=ItemTemplate}"
Content="{TemplateBinding Content}"
Command="{Binding MarkAsReadCommand}"
Foreground="Red">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsMarkedAsRead}" Value="True">
<Setter TargetName="ContentPresenter" Property="Foreground" Value="Green" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type Notification}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks a lot #BionicCode for the comprehensive answer. I ended up going with another solution which may or may not be good convention; I am a hobbyist.
Firstly, I don't need databacking / persistence.
Concerning the data model solution and overriding ListBoxItem.Template, I am using a prededfined class 'SyndicationItem' as the data class (my app is Rss Reader). To implement your datamodel solution I guess I could hack an unused SyndicationItem property, or use SyndicationItem inheritance for a custom class (I'm guessing this is the most professional way?)
My complete data model is as follows:
ObservableCollection >>> CollectionViewSource >>> ListBox.
Anyway I ended up using some simple code behind which wasn't so simple at the time:
First the XAML:
<Window.Resources>
<CollectionViewSource x:Key="fooCollectionViewSource" Source="{Binding fooObservableCollection}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="PublishDate" Direction="Descending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style x:Key="DeselectedTemplate" TargetType="{x:Type ListBoxItem}">
<Setter Property="Foreground" Value="Gray" />
</Style>
</Window.Resources>
<ListBox x:Name="LB1" ItemsSource="{Binding Source={StaticResource fooCollectionViewSource}}" HorizontalContentAlignment="Stretch" Margin="0,0,0,121" ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<TextBlock MouseDown="TextBlock_MouseDown" Grid.Column="0" Text="{Binding Path=Title.Text}" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Column="1" HorizontalAlignment="Right" TextAlignment="Center" FontSize="11" FontWeight="SemiBold"
Text="{Binding Path=PublishDate.LocalDateTime, StringFormat='{}{0:d MMM, HH:mm}'}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now the code behind:
Solution 1: this applies a new style when listboxitem is deselected. Not used anymore so the LB1_SelectionChanged event is not present in the XAML.
private void LB1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count != 0)
{
foreach (var lbItem in e.RemovedItems)
{
//get reference to source listbox item. This was a pain.
int intDeselectedItem = LB1.Items.IndexOf(lbItem);
ListBoxItem lbi = (ListBoxItem)LB1.ItemContainerGenerator.ContainerFromIndex(intDeselectedItem);
/*apply style. Initially, instead of applying a style, I used mylistboxitem.Foreground = Brushes.Gray to set the text color.
Howver I noticed that if I scrolled the ListBox to the bottom, the text color would revert to the XAML default style in my XAML.
I assume this is because of refreshes / redraws (whichever the correct term). Applying a new style resolved.*/
Style style = this.FindResource("DeselectedTemplate") as Style;
lbi.Style = style;
}
}
}
Solution 2: The one I went with. Occurs on SelectedItem = true, same effect as your first suggestion.
private void TextBlock_MouseDown(object sender, MouseButtonEventArgs e)
{
TextBlock tb = e.Source as TextBlock;
tb.Foreground = Brushes.Gray;
}
I know how to Align Textbox, but in the case of a WrapPanel, with several Textboxes inside, I couldn't manage to find a way to Align the whole text on center, also tried to make a TextBlock, with inside TextBlocks, but no way :
<GridViewColumn Header="{x:Static p:Resources.Priorite}" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<WrapPanel HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" TextAlignment="Center">
<TextBlock Text="{Binding PrioriteMin}"/>
<TextBlock Text=" - " />
<TextBlock Text="{Binding PrioriteMax}"/>
</TextBlock>
</WrapPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Your problem is that when the FrameworkElement which displays your cell content is created it copies the alignment properties from the corresponding ListViewItem. Which leads to HorizontalAlignment.Left.
Here you can read more about what each value does: https://learn.microsoft.com/de-de/dotnet/framework/wpf/advanced/alignment-margins-and-padding-overview
So to achieve what you want you have to set HorizontalContentAlignment to Stretch on your ListViewItem.
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
See also In WPF Stretch a control to fill a ListView Column
You can use styles to target the TextBlocks within a WrapPanel:
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<TextBlock>MyText</TextBlock>
<TextBlock>MyText2</TextBlock>
<WrapPanel>
I think the above should work in your case, though I may have misunderstood the requirement.
The example you gave seems to center align horizontally just fine!
<WrapPanel HorizontalAlignment="Center">
does the trick, and you don't even need
HorizontalAlignment="Center" TextAlignment="Center"
for your textBlock
I'm trying to create mutiple PivotItems dynamically in the c# end and then for every PivotItem I want the set the content as a GridView which has a Datatemplate where elements are bound dynamically.
XAML code:
<Page.Resources>
<GridView x:Key="pivoteItemGV"
Margin="18,10,0,0"
Background="#191919"
SelectionMode="None">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel MaxWidth="300">
<TextBlock x:Name="ItemNameTB" Text="{Binding ItemName}"/>
<TextBlock x:Name="ItemDescriptionTB" Text="{Binding ItemDescription}"
TextWrapping="WrapWholeWords"/>
<TextBlock x:Name="ItemPriceTB" Text="{Binding ItemPrice}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Page.Resources>
And on the C# end
private void BindTheContentToPage()
{
foreach(var categoryItem in contentResourceDictionary)
{
PivotItem categoryPivotItem = new PivotItem();
categoryPivotItem.Header = categoryItem.Key;
GridView gv = this.Resources["pivoteItemGV"] as GridView;
categoryPivotItem.Content = gv;
categoryPivotItem.DataContext = categoryItem.Value;
mainContentPivot.Items.Add(categoryPivotItem);
}
}
The app crashes at categoryPivotItem.Content = gv; stating Value does not fall within the expected range.
Is what I'm doing right does? As I'm not sure whether multiple copies of Page.Resource contents, GridView in this case is duplicated.
Thanks for the help in advance.
Don't put visual elements directly into the Resources section as you did. You can put DataTemplates there.
You need to set the PivotItem.ContentTemplate property to some DataTemplate. And set the PivotItem.Content property to your data object you want to bind to.
XAML code:
<Page.Resources>
<DataTemplate x:Key="pivoteItemGVTemplate">
<GridView
ItemsSource="{Binding}"
Margin="18,10,0,0"
Background="#191919"
SelectionMode="None">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel MaxWidth="300">
<TextBlock x:Name="ItemNameTB" Text="{Binding ItemName}"/>
<TextBlock x:Name="ItemDescriptionTB" Text="{Binding ItemDescription}"
TextWrapping="WrapWholeWords"/>
<TextBlock x:Name="ItemPriceTB" Text="{Binding ItemPrice}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</DataTemplate>
</Page.Resources>
And in C# codebehind:
private void BindTheContentToPage()
{
foreach(var categoryItem in contentResourceDictionary)
{
PivotItem categoryPivotItem = new PivotItem();
categoryPivotItem.Header = categoryItem.Key;
var template = this.Resources["pivoteItemGVTemplate"] as DataTemplate;
categoryPivotItem.ContentTemplate = template;
categoryPivotItem.Content = categoryItem.Value;
mainContentPivot.Items.Add(categoryPivotItem);
}
}
If your categoryItem.Value is a List of data objects, then add an attribute to the GridView (as I did in the XAML):
ItemsSource="{Binding}"
I'm creating a ListBox in my C# code, and I need to write this code in C#, although I'm not exactly sure how to do it,
http://smehrozalam.wordpress.com/2011/01/31/silverlight-and-wpf-how-to-make-items-stretch-horizontally-in-a-listbox/
<Grid>
<Grid.Resources>
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Grid.Resources>
<ListBox Margin="100" ItemsSource="{Binding MyList}"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"
Background="LightYellow"
TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
More specificly, how do I write this in C#
<Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
And
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
You can create the style via:
Style style = new Style(typeof(ListBoxItem));
style.Setters.Add(new Setter(Control.HorizontalContentAlignment, HorizontalAlignment.Stretch));
You can then set this via:
listBox.ItemContainerStyle = style;
The answer posted by Reed Copsey will work.
A naive alternative (though honestly likely more "efficient," since it's explicit) is to set the HorizontalContentAlignment property of your ListBoxItem objects to HorizontalAlignment.Stretch, when you construct them (if you're also constructing them in code).
ListBoxItem i = new ListBoxItem { HorizontalContentAlignment = HorizontalAlignment.Stretch };
Im getting menuItem icon appearing only on last menuItem.
If i snoop the app only last menuItem has image in icon, while if i debug all MenuItems appear to have image in icon. Also if i add submenuItem the icon on menuItem dissapears once i open submenus and the last submenu gets the icon... Any idea? PS: also tooltips on menu item dont work.
Im using caliburn micro and fluent ribbon controls.
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding DisplayName}"/>
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding Path=IconPath}"/>
</Setter.Value>
</Setter>
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
You are setting Icon property to an Image control in Style. Now, only one copy of Style is created and thus, only one copy of Image is created. Now, any control can have only one parent at a time. So, when it is assigned to last MenuItem, it is removed from previous MenuItem controls. To fix this, use Templates.
Instead of setting Header property, set HeaderTemplate:
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Path=IconPath}" />
<TextBlock Grid.Column="1"
Text="{Binding DisplayName}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
I'm not sure of what properties are exposed by the control toolkit you are using. But, I'm sure they must have a template property.
After doing this, you don't need to set Icon property in style.
I successfully use the following entries in a ResourceDictionary:
<!-- Define non-shared image to avoid loss of menu icons -->
<Image x:Key="MenuIconImage" Height="16" Width="16" x:Shared="false">
<Image.Source>
<DrawingImage Drawing="{Binding Icon}" />
</Image.Source>
</Image>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding DisplayName />
<Setter Property="Icon" Value="{StaticResource MenuIconImage}" />
</Style>
Works like this:
<DataTemplate x:Key="MenuItemHeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=IconPath}" />
<Label Grid.Column="1" Content="{Binding DisplayName}" />
</Grid>
</DataTemplate>
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{StaticResource MenuItemHeaderTemplate}" />
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
For some reason approach when Image is static resource with x:Shared = false doesn't work for me. Only last menu item shows icon. I've tried both StaticResource and DynamicResource. Here is my solution:
public class MenuItemIconHelper
{
#region ImageSource Icon
public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof(ImageSource), typeof(MenuItemIconHelper), new PropertyMetadata(default(ImageSource), IconPropertyChangedCallback));
private static void IconPropertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var i = (MenuItem)obj;
if (e.NewValue != null)
i.Icon = new Image() {Source = (ImageSource)e.NewValue};
else
i.Icon = null;
}
public static void SetIcon(DependencyObject element, ImageSource value)
{
element.SetValue(IconProperty, value);
}
public static ImageSource GetIcon(DependencyObject element)
{
return (ImageSource)element.GetValue(IconProperty);
}
#endregion
}
Sample:
<Style x:Key="CommandMenuItemStyle" TargetType="MenuItem">
<Setter Property="cb:MenuItemIconHelper.Icon" Value="car1.png" />
<Setter Property="Header" Value="{Binding Name}" />
I consider it to be more readable than using resource and you don't need to change MenuItem's HeaderTemplate. You can also implement some caching mechanism for ImageSource or Image.
1. Add Existing File... image file to resources (if you already have one, skip it).
2. In Solution Explorer select this image file.
3. Change Build Action to Resource.
And finally, you can add this image to XAML with simple call:
<Window.Resources>
<ContextMenu x:Key="contextMenu" >
<MenuItem Header="Restart" Name="menuItemRestart" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/restart.png"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Exit" Name="menuItemExit" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/window_close.png"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Window.Resources>
The Result: