WPF Context Menu for each Row in a GridView - wpf

I have a GridView which is has several columns which are all bound to a data structure in my ViewModel layer, so what I want to do is to
Have a Context Menu that will pop up when the user clicks on one of the rows of data in the GridView (so far I have only been able to make the context menu appear when I click on the Header of the column I put it on.
Have the contents of the context menu vary depending on the contents of the current row the user has clicked on.
Any suggestions? thanks

You can set up the ContextMenu in the ItemContainerStyle, to make it dependent on the row you could employ some DataTriggers.
Edit: Structure should take the following form if you decide to swap out the whole ContextMenu on a condition:
<Style TargetType="{x:Type ListViewItem}">
<Style.Resources>
<ContextMenu x:Key="DefaultMenu">
<!-- Rest of menu here -->
</ContextMenu>
<ContextMenu x:Key="NonDefaultMenu">
<!-- Rest of menu here -->
</ContextMenu>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="SomeValue">
<Setter Property="ContextMenu" Value="{StaticResource NonDefaultMenu}"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="ContextMenu" Value="{StaticResource DefaultMenu}"/>
</Style>
You normally could also define the ContextMenu in place as well but if you add event-handlers it gets buggy, the syntax would be:
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<!-- Rest of menu here -->
</ContextMenu>
</Setter.Value>
</Setter>

Related

How do I point to a Context Menu's parent in XAML?

I've searched quite a bit but most of the solutions I found were from people asking on how to do it in C#. I wanted both to practice my XAML and reduce my already messy C# code so I decided to try it on XAML.
What I've Tried:
I've created a template for the Context Menu and here's my code:
<ControlTemplate TargetType="ContextMenu">
<Grid>
<!-- Some Content -->
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Parent.IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
My understanding of the code:
I imagined the hierarchy to be like this:
Parent UIControl (Can be a button, textbox, etc)
|---> Context Menu
|---> Template
So with my code Binding Parent.IsMouseOver, RelativeSource={RelativeSource TemplatedParent}, I expected it to work like this:
I've set my source to the TemplatedParent first (which I expected to be the Context Menu)
Now I'm trying to bind to the Parent (which is a UIControl of whoever uses the Context Menu) of my source (which at the moment I expect to be the TemplatedParent)
Here's the full code of everything involved with this:
Parent:
<Button Content="Button" ContextMenu="{DynamicResource ErrorPopup}"/>
Context Menu w/ Control Template (They're stored on a ResourceDictionary since they'll be reused by other controls to display each of their own Errors):
-
<ContextMenu x:Key="ErrorPopup" x:Shared="False">
<ContextMenu.Style>
<Style TargetType="ContextMenu">
<Setter Property="IsOpen" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContextMenu">
<Grid>
<!-- Some Content -->
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Parent.IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ContextMenu.Style>
</ContextMenu>
What I expect:
I expected the Context Menu to popup when I place my cursor on top of the Button.
UPDATE:
I've managed to make some progress, I tried changing my binding to:
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=Button,Mode=FindAncestor}}" Value="True">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
Unfortunately it's displaying some weird behavior wherein the MouseOver would show the context menu ONLY if I forcibly open a Context Menu beforehand by using my mouse's right click.
This doesn't work:
I hover over the Button
Context Menu doesn't show up
This works:
I right click the Button
Context Menu shows up
Closes quickly
Shows a Context Menu from the Mouse Over
I move the cursor away = Context Menu disappears
I move the cursor inside = Context Menu appears
Could this be because the context menu doesn't actually exist yet up until I explicitly open it through a mouse's right click? And after initializing it, only then will the triggers work?

MenuItem.Icon appears only on the last item

I have a Menu with the following style on the items:
<Style TargetType="MenuItem">
<Setter Property="Template" Value="{StaticResource MenuItem}"/>
<Setter Property="Icon">
<Setter.Value>
<TextBlock FontWeight="Bold">Ic</TextBlock>
</Setter.Value>
</Setter>
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ItemsSource" Value="{Binding SubItems}"/>
</Style>
(Name, and SubItems are properties of the class MenuItem.
the ItemsSource property of the menu is bound to an object of type List<MenuItem> )
problem is the "Icon" part appears only on the last item of the menu:
Furthermore, if I click to expand the "Playlist" Item here's what happens:
I ultimately want to bind each Item to its own Icon, but that doesn't seem to work either.
Any ideas what causes this misbehavior and how to fix it?
update I've seen this: MenuItem style with icon creates only one icon
but it didn't work for me because a. x:Shared=false made a XamlParseException and b. if I moved it out of the Style.Resources it didn't make an exception but simply didn't work. Note that I do need this INSIDE the Style because ultimately I want it to be bound to a property of the class I'm binding the MenuItem to.
Don't Share the TextBlock
<TextBlock x:Key="tb" x:Shared="false" FontWeight="Bold">Ic</TextBlock>
<Style TargetType="{x:Type MenuItem}">
...
<Setter Property="Icon" Value="{StaticResource tb}"/>
</Style>

WPF : Custom DataGridRows with Custom style

i have a DataGrid, i want to add each row of this DataGrid, one by one, then check the information in that Row, if something in it (lets call it cancel) is true, that row is colored black, otherwise its colored white.
i'd also want to know how the contents in that row can be modified, like set text of each cell in it, and then add it to the DataGrid.
To fill and modify a DataGrid you bind an ObservableCollection to the DataGrid's ItemSource Property.
You can then modify the Collection as you like. For the layout you can use a Style.
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Cancel}" Value="false">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<!--
...
-->
</DataGrid>

Problems with WPF ToolTipService properties (BetweenShowDelay, etc.) in Microsoft.Windows.Controls.Ribbon control

I have an very, very huge application in WPF with a Ribbon in it. The ribbon contain a bunch of RibbonControls, each binded to a different command. On every control, we put a ToolTip. We overrided these ToolTip templates to use a control of our own, which gives more information. We could call il a superToolTip.
The override of the tooltip templates is working just fine. Now we want to unify the way the tooltips are showing. What I mean is we want the same initialShowDelay, ShowDuration, etc., for every single tooltips in the application (there are tooltips elsewhere than in the ribbon, which use the same home made control that the ones of the ribbon). So, I binded the ToolTipService.InitialShowDelay, ToolTipService.BetweenShowDelay, ToolTipService.ShowDuration properties to global constants in the application.
InitialShowDelay :
The property InitialShowDelay is working just fine for almost every control in the application... The only one not working is RibbonSplitButton, which keep the default value of 400...
BetweenShowDelay :
The property BetweenShowDelay is working just fine when the tooltip is on a ListBoxItem... but not working in the Ribbon nor in a complex control of our own (a Property Grid).
These properties are set in the control on which the tooltip is set, and not on the tooltip themselves.
I honestly have absolutely no idead why it is behaving this way... Anyone has any idea on what could cause this or how to solve it?
If you need more information, do not hesitate to ask, i really am desperate about this.
Thank you very much!
The problem was that the condition for BetweenShowDelay wasn't respected, you need to have a value set for the property "ToolTip", in this case you were using a template, so the value was at null. You can resolve it this way :
<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Utils:ToolTipControl DataContext="{Binding ToolTipInfo}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and then place the dummy in the specified button :
<!-- RibbonButton -->
<Style TargetType="{x:Type ribbon:RibbonButton}" BasedOn="{StaticResource RibbonControlStyle}" >
<Style.Triggers>
<DataTrigger Value="true" >
<DataTrigger.Binding>
<Binding Converter="{StaticResource IsBoundConverter}" />
</DataTrigger.Binding>
<Setter Property="Command" Value="{Binding}" />
<Setter Property="ribbon:RibbonControlService.Label" Value="{Binding Name}" />
<Setter Property="ribbon:RibbonControlService.SmallImageSource" Value="{Binding Icon}" />
<Setter Property="ribbon:RibbonControlService.LargeImageSource" Value="{Binding LargeIcon}" />
<Setter Property="Visibility" Value="{Binding Visibility}" />
<Setter Property="ToolTip" Value="dummy"/> <!-- Use dummy value to force tooltip to show and to Bind the tooltip-->
</DataTrigger>
<DataTrigger Value="false" >
<DataTrigger.Binding>
<Binding Converter="{StaticResource IsBoundConverter}" />
</DataTrigger.Binding>
<Setter Property="Background" Value="#FF900000" />
</DataTrigger>
</Style.Triggers>
</Style>
That way the dummy value will be override.
:D
Here is some code showing how I implemented my ToolTips
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
...>
...
<!-- Ribbon Tooltips Style -->
<Style TargetType="ribbon:RibbonToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Utils:ToolTipControl DataContext="{Binding ToolTipInfo}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
<!-- RibbonControl -->
<Style x:Key="RibbonControlStyle">
<Setter Property="ribbon:RibbonControlService.ToolTipTitle" Value="dummy" /><!-- Use dummy value to force tooltip to show -->
<Setter Property="ToolTipService.InitialShowDelay" Value="{x:Static Utils:ToolTipViewModel.ToolTipInitialDelay}"/>
<Setter Property="ToolTipService.ShowDuration" Value="{x:Static Utils:ToolTipViewModel.ToolTipShowDuration}"/>
<Setter Property="ToolTipService.BetweenShowDelay" Value="{x:Static Utils:ToolTipViewModel.ToolTipBetweenShowDelay}"/>
<!-- This style is used to select the "Editors" tab when opening Editor without a world, and to select the "Home" tab otherwise -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsWorldLoaded, Source={x:Static ViewModels:ViewportSettingsViewModel.Instance}}" Value="false">
<Setter Property="ribbon:Ribbon.SelectedIndex" Value="2"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsWorldLoaded, Source={x:Static ViewModels:ViewportSettingsViewModel.Instance}}" Value="true">
<Setter Property="ribbon:Ribbon.SelectedIndex" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
Also, for the splitbutton problem it was that the tooltipservice werent set for the child of the splitbutton ( the two parts) wich are named PART_HeaderButton and PART_ToggleButton. So even if you create your own style it will be override by the style of the ribbonsplit button ( see this link for the splitbutton.xaml file :
https://wpfcontrolextension.svn.codeplex.com/svn/trunk/Common/RibbonControlsLibrary/v3.5/Themes/Generic.xaml
So to bypass this overriding problem ( because we don't have access to the part directly we have to go via code. Im my case, I overrided the RibbonSplitButton class and the OnLoadTemplate method. That way with getchild we can access the property of the part and change them.
public partial class DuniaRibbonSplitButton : RibbonSplitButton
{
public DuniaRibbonSplitButton()
{
InitializeComponent();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var HeaderButton = base.GetTemplateChild("PART_HeaderButton");
var ToggleButton = base.GetTemplateChild("PART_ToggleButton");
OverrideAttributes(HeaderButton as Control);
OverrideAttributes(ToggleButton as Control);
}
private void OverrideAttributes(Control control)
{
control.ToolTip = "Dummy";
ToolTipService.SetInitialShowDelay(control, ToolTipViewModel.ToolTipInitialDelay);
ToolTipService.SetShowDuration(control, ToolTipViewModel.ToolTipShowDuration);
ToolTipService.SetBetweenShowDelay(control, ToolTipViewModel.ToolTipBetweenShowDelay);
}
}

ControlTemplate and validation - How to position items?

I created ControlTemplate which is shown if there are validation error on my textbox. My controltemplate looks like that
<ControlTemplate x:Key="TextBoxErrorTemplate">
<TextBlock Foreground="Orange" FontSize="12pt">Field can't be empty</TextBlock>
</ControlTemplate>
However if validation errors occure textBlock appears on textBox - and the user can't enter proper value. Is there any way to set the position of TextBlock - the one which shows error info?
ErrorTemplates are for adorning the control and not for changing its internal properties, to do this you should use a style with the respective trigger:
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Foreground" Value="Orange"/>
<Setter Property="FontSize" Value="12"/>
</Trigger>
</Style.Triggers>
</Style>
If you want to display some text you could use a template like this:
<ControlTemplate x:Key="TextBoxErrorTemplate">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<TextBlock Foreground="Orange" FontSize="12pt">Field can't be empty</TextBlock>
</StackPanel>
</ControlTemplate>
The TextBlock will be displayed on the right of the TextBox.
If you just want to show error messages i'd suggest you set the tooltip of the TextBox and bind it to the validation errors.

Resources