Caliburn Micro Action in ContextMenu with ControlTemplate - c#

I have a custom component with a ContextMenu in a ControlTemplate. I've read a couple of posts with people having trouble getting their actions to work in a ContextMenu. I've tried the workarounds posted there but I cannot get it to work. I keep getting No target found for method Open. None of these solutions used a ControlTemplate, however. I've tried to bind the model to the contextmenu, to the menuitems, to use TargetWithoutContext property, but none seem to work.
<components2:ImageBlock Background="Transparent" x:Name="ShareButton" Margin="0,0,7,0" >
<components2:ImageBlock.Style>
<Style TargetType="{x:Type components2:ImageBlock }">
<Setter Property="ToolTipService.IsEnabled" Value="False"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type components2:ImageBlock }">
<Grid x:Name="ContentGrid" Background="{TemplateBinding Background}" MinHeight="30" ToolTip="{TemplateBinding ToolTip}">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Facebook" cal:Message.Attach="Open(0)"/>
<MenuItem Header="Twitter" cal:Message.Attach="Open(1)]" />
<MenuItem Header="Tumblr" cal:Message.Attach="Open(2)]" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</components2:ImageBlock.Style>
</components2:ImageBlock>
How can I get the Open to work at my viewmodel behind the view?

Got it to work through a DependencyObject that is added to the ImageBlock, where I had to set the PlacementTarget of the ContextMenu to the ImageBlock. Strangely, setting the PlacementTarget of the ContextMenu through {Binding ...} to ImageBlock directly didn't work.

Related

How to remove ContextMenu border

So im trying to make button appear on right click in my ListBox.
<ListBox Grid.Column="1" Margin="358,44,20,63" Name="scriptbox" Background="#FF282828" Foreground="White" SelectionChanged="Scriptbox_SelectionChanged" BorderThickness="0">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem
Template="{DynamicResource MenuItemTemplate}"
Header="Delete"
Click="MenuItemDelete_Click" >
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
This is my MenuItem template.
<ControlTemplate TargetType="{x:Type MenuItem}" x:Key="MenuItemTemplate">
<Border x:Name="Border" Background="#FF282828" Padding="30,5,30,5" BorderThickness="0" Margin="0">
<ContentPresenter ContentSource="Header" x:Name="HeaderHost" RecognizesAccessKey="True" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter Property="Background" TargetName="Border" Value="#51544e"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ItemsPanelTemplate x:Key="MenuItemPanelTemplate">
<StackPanel Margin="-3,0,0,0" Background="White"/>
</ItemsPanelTemplate>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="ItemsPanel" Value="{StaticResource MenuItemPanelTemplate}"/>
</Style>
Everything is fine but there is white border all around the button.
What you recognize as a white border is the ContextMenu itself that contains your MenuItems. Try adding more buttons and changing the Background and BorderBrush of the ContextMenu and you will see.
<ContextMenu Background="Red" BorderBrush="Blue">
Changing the brushes like this will lead to this result, which makes it obvious.
If you create a custom control template for your MenuItems you should probably do so for the ContextMenu, too, if only setting brushes does not fit your requirements. As you can see in the example, there is still a vertical white line that is part of the default control template, that you might want to get rid of. You can start from this example, although it is neither the default template nor complete. Look at this related post for guidance on how to extract the default control template for ContextMenu if you need it.

ContextMenu based on object binding wpf

So I have a list view that displays post items (Delivery date, type, tracking number etc) and I have a context menu set up that either opens up the tracking website or copies the tracking number to the clipboard.
What I want is for the contextmenu only to appear for listitems that have a tracking number. I've got the idea of changing the visibility of the contextmenu but it's the binding to the tracking number I'm having the trouble with.
<ContextMenu x:Key="MyElementMenu">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
</Style>
This is what I have currently.
<MyControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</MyControl.Resources>
<!--Sets a context menu for each ListBoxItem in the current ListBox-->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu IsEnabled="{Binding HasTrackingNumber}" Visibility="{Binding HasTrackingNumber, Converter={StaticResource BoolToVis}">
<MenuItem Header="Track Item" Click="MenuItem_Click"></MenuItem>
<MenuItem Header="Copy to Clipboard" Click="MenuItem_CopyToClipboard"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
This should give you what you need. Not sure if you use the ContextMenu elsewhere, but if you dont you can always just set it in the style of the ListViewItem Style. Then you dont need to have it referenced from elsewhere. Either way its more about adding the Binding from the item. In your ListItem Viewmodel you could add something like:
public bool HasTrackingNumber => TrackingNumber == 0 || TrackingNumber == null;
(I dont know what type your tracking number is so you can do your own logic checks to know if it "has" a valid tracking number)
That seems like a case for a trigger:
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyElementMenu}"/>
<Style.Triggers>
<!--
Maybe the tracking number property is called something else, maybe it's 0
instead of null when absent. You didn't say.
-->
<DataTrigger Binding="{Binding TrackingNumber}" Value="{x:Null}">
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Style for TabItem's content

I have TabControl which is used as a main element of my app:
Every tab content would have a title (duplicate to the menu name), separater and content. My XAML code which must correspond to this view should be like this:
<TabControl Style="{StaticResource MyCustomStyle}">
<TabItem Header="Menu 1">
<TabItem.Content>
...
</TabItem.Content>
</TabItem>
<TabItem Header="Menu 2">
<TabItem.Content>
<TextBlock>Content</TextBlock>
</TabItem.Content>
</TabItem>
...
</TabControl>
To avoid duplicating parts of the code, I decided to setup view in custom style.
MyCustomStyle for this:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="Content">
<Setter.Value>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentPresenter ContentSource="..."/>
</StackPanel>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
The only problem occurs with the modification of content. Label don't want to bind value to the TabItem's header. The same story for ContentPresenter
I tried to use RelativeSource for this, but this is not working:
<Label FontSize="20" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}"/>
If I understand correctly, you're very close -- you just want to give it a ContentTemplate instead of setting the Content in the style:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label FontSize="20" Content="..."/>
<Separator/>
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>
Now, for the Label content. You can't get that via RelativeSource AncestorType because TabItem isn't in the visual tree: If you write a VisualTreeHelper.GetParent() loop, you find that the parent chain hits a bunch of random stuff like Grids and whatnot, then suddenly it's on TabControl.
So what we do instead, is we write a multi-value converter. We give it the DataContext for the DataTemplate -- that's the Content for the TabItem being ContentTemplated -- and the TabControl. Then we look through the TabControl's Items to find the TabItem that has the same Content.
I tried this as a regular valueconverter, just passing in {RelativeSource Self} from the Label, walking the visual tree inside the converter to find the TabControl, and using the Label's DataContext inside the converter to identify the TabItem I wanted. That didn't work because of (I think) virtualization: The DataTemplate is instantiated once and those controls are reused. Since the value of {RelativeSource Self} is the same each time, and I didn't tell the Binding anything about DataContext, the value converter was only invoked for the first TabItem that was ever selected. The multi-value converter fixes that problem by explicitly binding to ., the DataContext.
This breaks if you use ItemsSource to populate the TabControl. In fact, it breaks if you populate the TabControl with anything but TabItems just like you did. But then, you couldn't be setting their Header properties otherwise, and if you were using ItemsSource, you'd have all kinds of good things to bind to and you wouldn't be contemplating nutty expedients like this one.
TabItemHeaderConverter.cs
public class TabItemHeaderConverter : IMultiValueConverter
{
// This is pretty awful, but nobody promised life would be perfect.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var tc = values[0] as TabControl;
var tabItemContent = values[1];
var tabItem = tc.Items.Cast<TabItem>().FirstOrDefault(ti => ti.Content == tabItemContent);
if (null != tabItem)
{
return tabItem.Header;
}
return "Unknown";
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the new XAML:
<Style x:Key="MyCustomStyle" TargetType="{x:Type TabControl}">
<Style.Resources>
<local:TabItemHeaderConverter x:Key="TabItemHeaderConverter" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="10"/>
<Setter Property="Width" Value="120"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="10">
<Label
FontSize="20"
>
<Label.Content>
<MultiBinding Converter="{StaticResource TabItemHeaderConverter}">
<Binding RelativeSource="{RelativeSource AncestorType=TabControl}" />
<Binding Path="." />
</MultiBinding>
</Label.Content>
</Label>
<Separator />
<ContentControl
Content="{Binding}"
/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="TabStripPlacement" Value="Left"/>
</Style>

Prevent ItemContainerStyle from overriding already set Style

Is there a way of preventing a ItemContainerStyle from overriding an already set Style (via <Style TargetType="{x:Type MenuItem}">) for instance ?
A style for a MenuItem is already defined within a ResourceDictionary XAML file, which is loaded on App startup :
<ResourceDictionary>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{DynamicResource TextForeground}"/>
.. and so on
</Style>
</ResourceDictionary>
I have the following MenuItem XAML definition. The MenuItem is wrapped inside a ContextMenu of a generic TextBlock (just worth mentioning I guess). All goes well with the menu itself, yet its children (the actual values of the Enum) get a different style, since ItemContainerStyle overrides it :
<MenuItem Header="DisplayType"
Name="DisplayTypeMenu"
ItemsSource="{Binding Source={StaticResource DisplayTypeValues}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="MenuItem.IsCheckable" Value="True" />
<Style.Triggers>
<Trigger Property="MenuItem.Header"
Value="{x:Static enums:DisplayType.Description}" >
<Setter Property="MenuItem.IsChecked" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The ItemContainerStyle stems from another question of mine.
The MenuItem is placed within other layers, the top layer being a custom ContentControl :
public class SomeGradientPanel : ContentControl
{
public SomeGradientPanel ()
{
DefaultStyleKey = typeof(SomeGradientPanel );
}
}
The code above seems to be a good candidate for the source of the problem !?
Thus, the complete structure is :
<SomeNameSpace:SomeGradientPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="SomeLabel">
<TextBlock.ContextMenu>
<ContextMenu>
<!-- The MenuItem code snippet from above !-->
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</SomeNameSpace:SomeGradientPanel>
Can I refer to the already defined Style for the MenuItem within the ItemContainerStyle ? The Style override only occurs on the children of the said MenuItem, the parent has the expected style.
Thank you for your input !
Have you tried
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">

WPF DataGrid Binding DataGridCell Content

This is hopefully going to be a really simple answer, I'm just not seeing the proverbial wood for the trees I think.
I've got a DataGridCell style in which I want to bind the content of the cell to the source property of an image, here's the XAML I'm using at the moment:
<Style x:Key="DataGridImageCellStyle" TargetType="{x:Type toolkit:DataGridCell}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridCell}">
<Border Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0"
SnapsToDevicePixels="True">
<Image Source="{Binding RelativeSource={RelativeSource AncestorType=toolkit:DataGridCell}, Path=Content}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that at the moment i'm binding the Image Source to Content.. which doesnt work, i've also tried Value, which didn't work!
So my question is, nice and simply.. what is the correct binding to use to get the cell's content into the source property of this image?
Thanks in advance!
Pete
If the column is a DataGridTextColumn then you might be able to bind to the Text property of the TextBlock that is its content:
<Image Source="{Binding RelativeSource=
{RelativeSource AncestorType=DataGridCell}, Path=Content.Text}" />
That's really a hack, though. If you want to display an image in a column, you should probably use a DataGridTemplateColumn:
<DataGridTemplateColumn Header="...">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding SomeProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Where SomeProperty is the property of your row object that has the image path.

Categories

Resources