Use DataTrigger to hide/show WPF TreeView ContectMenu - wpf

I have a TreeView in my WPF application and I want to show or hide the context menu depending on the type of the treeviewItem. TreeView is databound and populated using a HierarchicalDataTemplate. I can disable the context menu items in code in the rightmouse event.
But I want this to be done in XAML instead. What I tried is;
<Grid.Resources>
<ContextMenu x:Key="MyContextMenu">
<MenuItem Name="Menu1" Header="Add " Click="AddNew_Click" ></MenuItem>
<MenuItem Name="Menu2" Header="" Click="Menu2_Click"></MenuItem>
</ContextMenu>
</Grid.Resources>
In side the Treeview I have this code
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Document.DocumentType}" Value="P">
<Setter TargetName="icon" Property="Source" Value="../Images/P.png"/>
*<Setter Property="ContextMenu" Value="{StaticResource MyContextMenu}"/>*
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
But this gives runtime error 'object reference not set to an instance of object'. You can see the image is set with data trigger when DocumentType is 'P' and I want the context menu to be visible/enabled only when DocumentType is 'P' and hide/disabled otherwise.
Can this be done?

Another person had the same issue and he had gotten this answer from Microsoft forum. It doesn't use triggers, however the context is dynamic. I can use a variation of this to show my context menu only when I need it to depending on the bound data.
here is the link
I will mark this as answered, so it might help someone in the future.

Related

Toggle two different elements in DataTemplate using Style Triggers

I have an object in ViewModel whose properties are displayed by a datatemplate. The screen also has a button toggling the IsEditing flag in ViewModel, which should make the object properties editable, like the following:
Name should change from TextBlock to TextBox;
Color should change from colored rectangle to ComboBox with color options;
Category should change from TextBlock to ComboBox;
I know how to implement this with two completely independent DataTemplates, using a Style and a DataTrigger to toggle between them:
<ContentControl Content="{Binding FancyObject}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource DisplayTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsEditing, ElementName=UserControl}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
And currently the DisplayTemplate is like this:
<DataTemplate x:Key="DisplayTemplate" DataType="my:FancyObject">
<Border>
<DockPanel DataContext="{Binding Metadata}">
<Border>
<TextBlock Text="{Binding Name}"/>
</Border>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding FancyObjectCollection}">
<DataGrid.Columns>
<!-- Text and Template columns -->
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Border>
</DataTemplate>
The problem is: using two independent but similar templates would mean a duplication of layout, since only some fields would change, but the overall structure is the same.
Another option I imagine is to use a single template defined inside the Style, and use the Trigger to change the fields individually, but I don't know how to do it, or even if it is possible at all.
You can use one template.
In the template add both TextBlock and TextBox, same for all your other controls on the original template.
Bind the controls visibility to bool to visibility converter. (Or use triggers) Only one set of your control will be seen each time (based on IsEditing flag)
The ControlTemplate is only used upon generating the UI Elements. If you change the template AFTER generating the items, the generated items will not change.
You can also not use a trigger to change a TextBox to a TextBlock and vice versa.
Your only option is indeed to mirror the layout twice and hide/display it via the data bound property.

Force context menu to recreate child controls each time it opens

I have a context menu which contains a both a OneTime, one way binding and a second, non-OneTime two way binding to the same property. The goal is to have a color editor which displays the initial color value, and allows the user to change the selected value while still being able to compare it to the original.
This works well the first time the context menu is opened, but the menu doesn't seem to fully recreate itself each time it is opened (cached?). Instead, it "remembers" the original binding value, instead of doing another OneTime binding from the source to get the new "initial" value.
Is there a way to force a context menu to fully recreate its contents each time it is opened?
I was able to quickly do this by creating a Style for the ContextMenu that sets its DataContext to null when it is hidden. This will cause the Bindings to re-run when it is opened, as they will have a new source. If you set the DataContext explicitly for the ContextMenu, you'll have to move that to a setter:
<ContextMenu>
<ContextMenu.Style>
<Style TargetType="{x:Type ContextMenu}">
<Style.Triggers>
<Trigger Property="IsOpen" Value="False">
<Setter Property="DataContext" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</ContextMenu.Style>
<MenuItem Header="{Binding Color, Mode=OneTime}" />
</ContextMenu>

Switching between views according to state

Say I've an application that displays user friends list. The friends list is displayed in as a TabItem. The user has to Log in first to the server, in order to get the list of friends.
I've created two user controls, one for when the user is logged in, the the other when he is unlogged. something alone this line:
UnloggedView.xaml
<UserControl x:Class="UnloggedView" ...>
<TextBlock ...>You need to <Hyperlink Command="{Binding LoginCmd}">
Login</Hyperlink>too see your friends list</TextBlock>
</UserControl>
LoggedView.xaml:
<UserControl x:Class="LoggedView" ...>
...
<ListView ItemSource={Binding Path=friends}">...
</UserControl>
The main window has the following code:
....
<TabItem Header="Friends">
<vw:UnloggedView />
</TabItem>
I believe everything is according to the MVVM principal. The LoginCmd is a simplified variation of the DelegateCommand (from prism), implemented in the ViewModel. Both Views works fine, and as the list is populated (asynchronously), notification are fired and the View is updated. I'm happy.
So I've two questions: First question is how to I fire the LoginWindow (where the user is prompted to enter his credentials? For now, I simply create the LoginWindow (a view object) and presents it with ShowDialog. It appears like I'm breaking the rules of MVVM here by directly manipulating the UI from the ViewModel.
main question is after I log-in with the server, what is the correct way to replace the content of the TabItem with the LoggedView. According to the MVVM principals, the ViewModel shouldn't not have knowledge to the internals of the View. I expose IsLogged property in the ViewModel (which will fire PropertyChanged notification) but what should I bind to what in order to make everything happens? I really don't want the ViewModel manipulating the View.
Thanks
I see this question come up a lot, and have written something about switching between Views/UserControls here. Usually I use a ContentControl and switch out the ContentTemplate based on a DataTrigger, however the same principal works to switch a TabControl's ItemTemplate
<DataTemplate x:Key="LoggedOutTemplate">
<local:UnloggedView />
</DataTemplate>
<DataTemplate x:Key="LoggedInTemplate">
<local:LoggedView />
</DataTemplate>
<TabControl>
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="ItemTemplate" Value="{StaticResource LoggedOutTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoggedIn}" Value="True">
<Setter Property="ItemTemplate" Value="{StaticResource LoggedInTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
</TabControl>
You may have to use ElementName or RelativeSource in your DataTrigger binding to find the IsLoggedIn property in your DataContext
As for firing a Login Command from your Logged Out view, there are multiple ways of doing it.
My preferred method is to use some kind of messaging system such as MVVM Light's Messenger, or Microsoft Prism's EventAggregator, and firing some kind of ShowLoginDialog message when the button is clicked, then let whatever ViewModel is taking care of showing the login dialog subscribe to receive those messages and handle them.
Another way is simply use a RelativeSource binding to find the object in the Visual Tree that has the LoginCommand in it's DataContext, and bind to that.
You can see examples of both here
First I'll answer you second question... Just create an Enum as SessonState
enum SesionState
{
LoggedOut=0,
LoggedIn
}
After that create a property in your ViewModel for window called SessionState and Update that Property with the required value when you Login and LogOut.
Xaml required for switching views
<Window>
<Window.Resources>
<DataTemplate x:Key="LoggedOutView">
<ViewLayer: LoggedOutView/>
</DataTemplate>
<DataTemplate x:Key="LoggedInView">
<ViewLayer:LoggedInView/>
</DataTemplate>
<Style x:Key="mainContentControlStyle"
TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SessionState}"
Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedOutView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Mode}"
Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedInView}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl>
<TabItem>
<ContentControl Grid.Row="0"
Content="{Binding}"
Style="{StaticResource ResourceKey=mainContentControlStyle}">
</TabItem>
</TabControl>
</Grid>
</Window>
for your 1st question: you can simply use a ILogindialogservice from your viewmodel. i use the following for dialogs and mvvm. its "Unit Test"able and not breaking mvvm.
EDIT. in your viewmodel you would have then a line something like this:
var result = this.loginservice.ShowDialog("Login", loginvm);

WPF Button Visibility

I am using MVVM architecture to develop a WPF application...
So far everything has been going fine.
I have run into an issue with binding visibility. I want to minimize writing code in the code behind if i can but if it's REQUIRED then I don't mind doing it.
I have a ViewModel. THis model exposes a boolean and 2 commands. A connect command, a disconnect command, and a DeviceCurrentlyConnected Bool.
Basically I have decided to make 2 buttons but have the button visibility based on the boolean.
So i have had a hard time with this. I tried styles with triggers for a long time.
<Button Visibility="Hidden" Content="{x:Static UIStrings:ClientStrings.DeviceBar_DisconnectCommandName}" VerticalAlignment="Center" HorizontalAlignment="Center" Height="{Binding ElementName=this.Content, Path=DesiredHeight}" Margin="10" Name="Disconnect" Command="{Binding DisconnectCurrentDeviceCommand}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding DataCotext.DeviceConnected, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I can not get styles to work at all.
Basically the functionality that I want is:
DeviceConnected = false:
Display a button with content Connect and command bound to the ConnectCommand.
DeviceConnected = true:
Display a button with content disconnect and command bound to the DisconnectCommand.
for a button to be displayed and bound to the connect device when no device is currently connect and for a button to be displayed when a device is connected that is bound to the disconnect command and to say the word disconnect.
Write up a bool to visibility converter and then use the converter on your buttons. Five minute recipe for a decent BoolToVisibilityConverter is a good post to read up on creating/using a visibility converter.
What I've done in the past is use a bool to visibility converter and passed in the button's IsEnabled property as the parameter to the converter. Since the button is dis/enabled by the command in the model with the CanExecute method, you can then use the IsEnabled property to set the visibility of the button with the converter.
The reason that your trigger doesn't work is that the style is overridden by the attribute on the button itself.
You can use a converter as Metro Smurf suggests, alternatively you can move the visibility attribute into the style so that the trigger works properly
Just add:
<Style.Setters>
<Setter Property="Visibility" Value="Hidden" />
</Style.Setters>
To the style and then remove the attribute.

WPF Debugging datatriggers?

I am trying to do something very simple. I have a ToggleButton.IsChecked property bound to a bool. I want the background to toggle between red(false) and green(true). But for some reason it seems to be toggling between red and no background. I used a converter to check if I am getting proper notifications from the source and I am, so not sure why one trigger(false/red) works and the other(true/green) doesnt. Also would like to hear how people debug these kind of issues. Thanks!
Here is the code.
<DataTemplate x:Name"Flipper">
<StackPanel>
...
<ToggleButton IsChecked="{Binding Path=BoolValue,
Converter={StaticResource converter}}"
Name="onoff" >
</ToggleButton>
...
<StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=onoff,Path=IsChecked}"
Value="True">
<Setter TargetName="onoff" Property="Background" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=onoff,Path=IsChecked}"
Value="False">
<Setter TargetName="onoff" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Update: I changed the togglebutton to a checkbox and it works. No idea why...
Try using WPF Inspector:
https://wpfinspector.codeplex.com/
Once you attach to your running WPF application, highlight the element in question by holding down ctrl + clicking on it. Then, select the element in the visual tree (might be a parent) that contains the trigger. Click on the triggers tab and you can see the current evaluation (e.g. True == True). If the datatrigger condition is met, the little icon will be orange (lit).
It looks ok to me, can you try altering the converter to return "red" or "green" rather than True/False (and alter the trigger accordingly). I have seen some wierd behaviour with WPF triggers when using NULL or Booleans in that it "unsets" the property if it's the opposite of your trigger value, rather than using another trigger value.
As for debugging them.. I'd love to know if there's a better way than the hack and hope methods I generally use for XAML debugging :D

Resources