Use Style in TreeView HierarchicalDataTemplate - c#

I'm new to this and can't quit get the correct syntax. This works correctly to capture the Left Mouse click on the textbox within the treeview:
<HierarchicalDataTemplate
DataType="{x:Type r:NetworkViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NetworkIP}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
How can this be done using a Style block in the Resources?
The goal being to use the same style for all TextBoxes in the TreeView. Something that would sit in the Usercontrol.Resources and be refrenced by the HierarchicalDataTemplate.

If I understand you correctly, you could define a template in the controls or windows resources with a target type (opposed to key x:Key=...) to have it automatically applied to all items in the tree view.
Here is a small example with a definition of a template in the window resources, which contains the InputBindings definition. This template will be automatically applied to all objects of type ItemViewModel if no other template is explicitly defined by the ItemsControl or TreeView. In this example, the items are displayed in a simple ItemsControl but it works for the TreeView just the same.
Note that for this to work, all items in the TreeView need to be of the same type. It is not sufficient if they are derived from the same base type. The template will only be applied, if the type defined in Template.DataType is exactly the same as the type of the ViewModel. If your TreeViews ItemsScources contain mixed type, you would need to specify the template for every type separately.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:ItemViewModel}">
<TextBlock Text="{Binding Name}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding
MouseAction="LeftClick"
Command="{Binding SelectItem}" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}" />
</Grid>
</Window>

Related

Select different bindings in DataContext based on another binding

I have a fairly dynamic ObservableCollection of view models that is used by two different ListBox elements in XAML. Each view model contains properties for two different model objects of type Card called Primary and Secondary, as well as other properties. In one ListBox I'd like to display properties from Primary and in the other I'd like to display properties from Secondary. I'd like to use the same XAML UserControl file when displaying the ListBoxItems for both.
My first thought was to create an entry in UserControl.Resources that gives a name to the "right" card based on a RelativeSource reference from the parent view model which indicates Primary or Secondary, but I've not created an entry like that before. Is this the right approach? If so, what would the entry look like?
I've made up some XAML to help illustrate (may have typos). First, the Primary ListBox control:
<UserControl x:Class="Project.Cards.ListPrimary" d:DataContext="{Binding Main.Cards.Primary, Source={StaticResource Locator}}">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox x:Name="CardListBox"
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}" />
</Grid>
</UserControl>
And the secondary:
<UserControl x:Class="Project.Cards.ListSecondary" d:DataContext="{Binding Main.Cards.Secondary, Source={StaticResource Locator}}">
... (same) ...
</UserControl>
And the card view (where I need to replace "Primary.Direction" with something that lets me select Primary/Secondary):
<UserControl x:Class="Project.Cards.Card">
<UserControl.Resources>
... perhaps something here ...
</UserControl.Resources>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock Text="{Binding Primary.Direction}" />
</StackPanel>
</UserControl>
If you want two instances of the same UserControl that differ in one respect, you figure out how to parameterize that. There are a couple of ways, but the simplest I thought of that fits your case was to just bind the differing value to a property of the View. This moves the specification of the different value to the owner.
We'll do that by defining a dependency property on the UserControl. It's a string, though it could be an object, and in the future you might want to make it one. Since we're using the view in a DataTemplate, we can bind a property of the DataContext to it there.
public partial class Card : UserControl
{
public Card()
{
InitializeComponent();
}
public String Direction
{
get { return (String)GetValue(DirectionProperty); }
set { SetValue(DirectionProperty, value); }
}
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register("Direction",
typeof(String), typeof(Card), new PropertyMetadata(null));
}
...and we'll use that in the UserControl like this:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock
Text="{Binding Direction, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</StackPanel>
</Grid>
The RelativeSource stuff tells the Binding to look for that Direction property on the UserControl object itself, rather than on the DataContext as it would otherwise do by default.
If Card.Direction were object instead of string, you'd make that TextBox a ContentControl and bind to its Content property. Then you could put anything in there -- XAML, a whole other viewmodel, literally anything that XAML can figure out how to display.
And here's how it looks in the wild:
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
And here's my whole mainwindow content from my test code. I didn't bother creating user controls for the listboxes; the above template is an exact match for the way you're doing it.
<Window.Resources>
<DataTemplate x:Key="PrimaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
<DataTemplate x:Key="SecondaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Secondary.Direction}" />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource PrimaryItemTemplate}"
/>
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource SecondaryItemTemplate}"
/>
</StackPanel>
</Grid>
I originally thought of a more elaborate scheme where you give the view a DataTemplate instead, and it worked, but this is simpler. On the other hand, that was more powerful. I actually used that in the first version of the answer, before I came to my senses; it's in the edit history.
Thanks for a fun little projectlet.

WPF TabControl Context menu get item right-clicked in mvvm

I have an application that displays a list of ViewModels as tab control items. The list of items is databound to a property on the parent ViewModel.
Now I want to add a context menu to support actions for each of the TabItems (not the whole TabControl itself).
This is the control in question:
<TabControl x:Name="Items"
Grid.Column="2"
Grid.Row="0"
Margin="3,5,5,3"
Visibility="{Binding Path=TabControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}" />
<Button Padding="10,0,0,0"
Content="X"
Style="{DynamicResource NoChromeButton}"
cal:Message.Attach="CloseTab($dataContext)" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
When I put the context menu code inside the TabControl tags the context menu iis registered for the TabControl as a whole. This is not the behaviour I want.
If I add it to the StackPanel tags inside the DataTemplate Tags, the DataTriggers registered for each Item is getting executed on the child ViewModel, but the view model does not have the methods and properties to execute the event.
Is there a possibility to solve this problem? How can I add a context menu to each item to support actions like: "Close This", "Save This", "Close Everything Except This"
Some more Infos:
I use Caliburn.Micro as framework and use it's conventions to bind the TabControl to the Items property on the ViewModel, which is an IObservableCollection<LocationDetailsViewModel> created by inheriting my ViewModel from Conductor<LocationDetailsViewModel>.Collection.OneActive. The LocationsDetailsViewModel also inherits from Screen
Everything works as intended. If I add an item to the Items property the TabControl gets updated properly.
I want to add a ContextMenu to each TabControl item, which is accessible by right-clicking the header. The context menu then should contain actions, like "Close This", "Save This", "Close Everything Except This"
For that I added a context menu to the StackPanel which controls the design of the header and used CM to call the appropriate method on the view model. But when I call it, I get an exception telling me that no suitable method can be found.
I double checked and it seems CM wants to call a method on the LocationDetailsViewModel and not the LocationViewModel, even though a similar method call exist in the close button for each tab item.
Here is the code with the context menu:
<UserControl x:Class="RpgTools.LocationPresenter.Views.LocationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:RpgTools.LocationPresenter.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:LocationViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True"
Padding="5">
<!-- Code left out for brevity -->
<TabControl x:Name="Items"
Grid.Column="2"
Grid.Row="0"
Margin="3,5,5,3"
Visibility="{Binding Path=TabControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Close This"
cal:Message.Attach="CloseTab($dataContext)">
</MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding DisplayName}" />
<Button Padding="10,0,0,0"
Content="X"
Style="{DynamicResource NoChromeButton}"
cal:Message.Attach="CloseTab($dataContext)" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
ContextMenus aren't part of the normal VisualTree the way other controls are, so the .DataContext isn't inherited as expected for binding purposes.
You need to bind the ContextMenu.DataContext to the ContextMenu.PlacementTarget.DataContext, which in this case would be StackPanel.DataContext.
<ContextMenu DataContext="{Binding
RelativeSource={RelativeSource Self},
Path=PlacementTarget.DataContext}">

WPF Determining DataTemplate object a user clicked on in ItemsControl

Assume a simple MVVM Application implemented in WPF:
There is a view with an ItemsControl that uses DataTemplates to resolve a view for various ViewModels in a collection.
What I want to know is, how would I add functionality to allow me to click on a given ViewModel in my ItemsControl to return that element in the container ViewModel?
That is to say, for the given example, I may want be able to click on my WrapPanel and then from my BarnYardViewModel have the particular ViewModel from the ItemSource tag returned. (much like binding the selected item in a ComboBox)
<UserControl x:Class="AGI_ServiceTool.View.DataMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MyProject.ViewModel">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:MooCowViewModel}">
<StackPanel>
<Image Source="/View/Images/MooCow.png"/>
<label content="This is a Moo Cow"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ChickenViewModel}">
<StackPanel>
<Image Source="/View/Images/Chicken.png"/>
<label content="This is a Chicken"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:HorseViewModel}">
<StackPanel>
<Image Source="/View/Images/SarahJessicaParker.png"/>
<label content="This is a Horse"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding MyAnimals}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ScrollViewer.CanContentScroll="True" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
And a few basic ViewModels:
namespace MyProject.ViewModel
{
class AbstractViewModel : INotifyPropertyChanged { ... }
class MooCowViewModel : AbstractViewModel {}
class ChickenViewModel : AbstractViewModel {}
class HorseViewModel : AbstractViewModel {}
class BarnYardViewModel : AbstractViewModel
{
public BarnYardViewModel()
{
_myAnimals.add(new MooCowViewModel());
_myAnimals.add(new ChickenViewModel());
_myAnimals.add(new HorseViewModel());
}
private ObservableCollection<AbstractViewModel> _myAnimals = new ObservableCollection<AbstractViewModel>();
public ICollectionView MyAnimals{
get { return System.Windows.Data.CollectionViewSource.GetDefaultView(_myAnimals); }
}
}
}
I would use a regular Button styled to have no UI, and would pass the ViewModel to it using the CommandParameter
For example,
<ControlTemplate x:Key="ContentOnlyTemplate" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=MyItemsControl, Path=DataContext.MyClickCommand}"
CommandParameter="{Binding }"
Template="{StaticResource ContentOnlyTemplate}"
Content="{Binding }" />
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
This will result in each item in your ItemsControl being rendered using a Button, and the Button will execute whatever command you specify and pass the current ViewModel in as the CommandParameter.
In this case, I've specified that the Button.Template should be overwritten to use a ContentPresenter which has no UI, so basically it will be whatever your DataTemplate for each animal is.
There are some other solutions posted at this related WPF question too if you're interested: What is the best way to simulate a Click, with MouseUp & MouseDown events or otherwise?
Any reason why it is a StackPanel yet you want something to know if it is Clicked?
I suggest you to change it to a Button and change the Template for it then hookup the Command property to a property in your ViewModel then said the CommandParamter as the Type

WPF UserControl not rendering. UserControl is instantiated using DataTemplates

I have a user control that I`m trying to show via data templates. Unfortunately, the control never shows. Here's the DataTemplate for it:
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
If I put regular controls inside those DataTemplate tags, I can see them. My SpecialControl however, won't show. Here's the xaml file for my SpecialControl:
<UserControl x:Class="CustomControls.SpecialControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<StackPanel>
<CheckBox Content="Hello World" />
<Button >
<TextBlock Text="Goodbye world"/>
</Button>
</StackPanel>
</UserControl>
For some reason, this control is invisible at run time. If I put them directly into the template, I'll see them. I know I could just do that, but I want to do something more complex with this class, using databinding, and custom behavior. I tried using Snoop, and I can see my SpecialControl in there somewhere, with a ContentPresenter that's not expandable: no sign of The checkbox or the button.
Edit:
Here is the View that is using the SpecialControl: I left out many of the templates I'm using, because I don't want this to be too crowded.
<UserControl x:Class="Tools.EditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:ctrls="clr-namespace:CustomControls"
xmlns:local="clr-namespace:Tools"
mc:Ignorable="d"
x:Name="This">
<UserControl.Resources>
<!--More templates -->
<DataTemplate x:Key="groupBoxTemplate" >
<ctrls:SpecialGroupBox Header="{Binding Path=Title}" Margin="2" DockPanel.Dock="Top">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector="{DynamicResource guiTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
<!-- Set up the width of the individual items based on how many columns we are supposed to have and what the adjusted width of the wrapPanel is. This way the 4th item will be placed on the 2nd row if the ColumnCount is 3. -->
<WrapPanel.ItemWidth>
<MultiBinding Converter="{StaticResource itemWidthConverter}">
<Binding Path="ColumnCount"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</WrapPanel.ItemWidth>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ctrls:SpecialGroupBox>
</DataTemplate>
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
<local:GuiTemplateSelector
... <!--More templates -->
GroupBoxTemplate="{StaticResource groupBoxTemplate}"
SpecialTemplate="{StaticResource listViewTemplate}"
x:Key="guiTemplateSelector"/>
</UserControl.Resources>
<DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel Name="rootPanel" DockPanel.Dock ="Top" Width="Auto" Height="Auto">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector=" {StaticResource guiTemplateSelector}">
</ItemsControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>
If you're wondering what the SpecialGroupBox does, it holds other controls, positioning them a certain way. There are a few of them in this window, and they work. It is inside of one of these SpecialGroupBoxes that my SpecialControl is supposed to appear.
Does your UserControl have a corresponding .xaml.cs file? This problem seems to happen when it doesn't.
If you're using Visual Studio, try copying all the XAML from your UserControl, then deleting it from your project, adding a new UserControl (with the same name as before), then pasting your content back into the XAML file. This will ensure that you have the correct .xaml.cs file set up.
See this answer.
In your post you didn't mention how you are using the listViewTemplate . if your using inside listview/listbox you can try like this it will load the user control:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate >
<ContentControl ContentTemplate="{StaticResource listviewDataTemplate}" Content="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Applying DataTemplate based on type

when i do the following and set the content of a Control to my ViewModel the Template gets applied automatically.
<DataTemplate DataType="{x:Type ViewModels:ViewModel}">
<StackPanel Orientation="Vertical">
<ControlX ....>
<ControlY ....>
</StackPanel>
</DataTemplate>
however i want to use FindResource to get the DataTemplate in the code behind so i had to add an x:key
<DataTemplate DataType="{x:Type ViewModels:ViewModel}" x:Key="{x:Type ViewModels:ViewModel}">
<DataTemplate DataType="{x:Type ViewModels:ViewModel}" x:Key="ViewModelTemplate">
But when i add an x:key the FindResource() works but the DataTemplate stops being applied automatically based on the type, any workaround available for this?
As a bad looking work around, you may try creating 2 DataTemplates that share the same content:
This ControlTemplate defines the shared content:
<ControlTemplate TargetType="{x:Type ContentControl}"
x:Key="MyControlTemplate">
<TextBlock Text="Some content" />
</ControlTemplate>
Then 2 DataTemplates as a workaround:
<DataTemplate x:Key="MyDataTemplate">
<ContentControl Template="{StaticResource MyControlTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type system:String}">
<ContentControl Template="{StaticResource MyControlTemplate}" />
</DataTemplate>
Edit:
I know that this answer came a year too late after I've provided a bad looking work around above, but it's better than never.
The implicit key set for an implicit data template is the data type itself wrapped in a DataTemplateKey.
You can either use:
FindResource(new DataTemplateKey(typeof (MainViewModel))
or
Resource[new DataTemplateKey(typeof (MainViewModel)]
to get your data template in code behind.
<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
This DataTemplate gets applied automatically to all Task objects. Note that in this case the x:Key is set implicitly. Therefore, if you assign this DataTemplate an x:Key value, you are overriding the implicit x:Key and the DataTemplate would not be applied automatically.
It works as documented. AFAIK you can either use Key or DataType not both, there may be workarounds which I wasn't aware of.

Categories

Resources