Styling different levels of a WPF treeview differently? - wpf

I have a very flat-structured treeview with only two levels of items - the main ones, and one level of subitems. I am using WPF MVVM and would like a way to style the two levels differently, but have no idea how.
I bind the treeview to an ObservableCollection in my ViewModel, and each element has one more ObservableCollection for the next level.
Any help?

This can be accomplished via DATABINDING and using DATATEMPLATES.
You would design two DataTemplates. 1 as a Hierarchical DataTemplate and the other as a standard version for your lower level (this is since you only utilize 2 levels)
Then set the ItemTemplate of your HierarchicalDataTemplate to the regular DataTemplate
Details can be found here: http://msdn.microsoft.com/en-us/magazine/cc700358.aspx
Code snippet from the above site:
<!-- ORDER DETAIL TEMPLATE -->
<DataTemplate x:Key="OrderDetailTemplate">
<TextBlock>
<Run>Product:</Run>
<TextBlock Text="{Binding Path=Product}" />
<Run>(</Run>
<TextBlock Text="{Binding Path=Quantity}" />
<Run>)</Run>
</TextBlock>
</DataTemplate>
<!-- ORDER TEMPLATE -->
<HierarchicalDataTemplate
x:Key="OrderTemplate"
ItemsSource="{Binding Path=OrderDetails}"
ItemTemplate="{StaticResource OrderDetailTemplate}"
>
<TextBlock Text="{Binding Path=Desc}" />
</HierarchicalDataTemplate>

Related

WPF MVVM Light dynamic views and datagrids

Okay I have a mildly complex piece of functionality here. I'd like to know A) If I am doing it properly. If not, what should I change? 2) If it is proper, what is the best solution to my problem?
I have a main window with a ListView of items. If I click one of these, the right hand Grid Column in this window should populate with a DataGrid with information on the item selected. If I click another item in the ListView, it should change to another DataGrid.
I have seen some ContentPresenter examples but I cannot get this to work, so I stripped it out and will show you the code I have so far. Right now, I only have one item setup, so I will stick with this Driver example.
DriverGrid.xaml
<UserControl DataContext="{Binding AdminDriver, Source={StaticResource Locator}}">
<Grid>
<DataGrid>
//stuff here for datagrid
</DataGrid>
<Button Content="Edit" Command="{Binding ShowEditWindow}" />
<Button Content="Add" Command="{Binding ShowAddWindow}"/>
</Grid>
</UserControl>
AdminDriver.cs (VM)
//Contains variables, and is the datacontext for the above usercontrol
AdminMain.xaml (view for all admin stuff)
//Contains a bunch of junk for the min admin screen which has the listview with the options in it. If you require this piece of code, I can trim it down but I don't se currently see it's relevance. The DataGrid belongs in this window, I'm assuming in a Content Presenter. Here is the ListView that is in column 1 of 2.
<ListView ItemsSource="{Binding AdminMenu}"
Name="AdminFields"
SelectionMode="Single">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand CommandParameter="{Binding SelectedItem, ElementName=AdminFields}" Command="{Binding registerSelected}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding FieldName}"/>
</ItemContainerTemplate>
</ListView.ItemTemplate>
</ListView>
I would take the binding of the DataContext out of the UserControl. This will allow you to use it in other spots if you ever find the need. Instead, just bind the DataContext where you are using it.
<view:YourUserControl DataContext="{Binding AdminDriver}" />
If you are looking to present different UserControls based upon which item is selected in the ListView, then you'll use a ContentPresenter. All the items in your ItemsSource will have the same base class or implement the same interface so that you can put them in the same ObservableCollection. I'll assume AdminDriver would be of that type/interface as well then.
You would set up some DataTemplates at the top of the Window that map the possible real types of the objects in your ItemsSource (AdminMenu) to the UserControl that would represent them.
<Window.Resource>
<DataTemplate DataType="{x:Type model:TypeA}">
<view:UserControlA />
</DataTemplate>
<DataTemplate DataType="{x:Type model:TypeB}">
<view:UserControlB />
</DataTemplate>
//rinse and repeat
</Window.Resource>
Then you'll add a ContentPresenter to the Grid and bind it's DataContext to the AdminDriver property. The UserControl matching the actual type of the selected item as mapped in your DataTemplates will appear.
<ContentPresenter Content="{Binding AdminDriver}" />

WPF TabControl, switching to another TabItem does not work, sticks to first TabItem

I have a control that contains following XAML code. It works fine excepted that I caInnot switch to another TabItem. I read that TabControl virtualizes the TabItem, I suspect the strange behaviour, namely that I cannot get to display any other TabItem as the first one, is related to this.
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate> <!-- header -->
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:Shared="False"> <!-- tabitem content -->
<controls:ItemControl Item="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I tried to set the x:Shared attribute of the DataTemplate to False but has not the expected effect. Is there a way to reach this without going the way of using a custom style and replacing the TabControl with an ItemsControl. I mean the functionality of TabControl is what I would like, I would like to simply use it with ItemsSource binding...
This behaviour will happen if you are binding to a collection that has duplicate objects in it.
Duplication can occur due to having added an object multiple times or because equality has been redefined for the objects in question.

Is there something in WPF similar to Style.BasedOn for DataTemplate?

At the moment, I have two very large DataTemplate objects to display two sets of items in two ListBoxes. The DataTemplates are referenced in the ContentTemplate property in two Styles that are set in the ItemContainerStyle properties of the two ListBoxes. The items are of the same type and the DataTemplates are identical except for the following control:
From DataTemplate1
<TextBlock Style="{StaticResource TextStyle}" FontSize="20" Foreground="White"
HorizontalAlignment="Left" Panel.ZIndex="2" Text="{Binding RemainingTime.TotalHours,
Converter={StaticResource DoubleToIntegerConverter}, StringFormat={}{0:#00}}" />
From DataTemplate2
<TextBlock Style="{StaticResource TextStyle}" FontSize="20" Foreground="White"
HorizontalAlignment="Left" Panel.ZIndex="2" Text="{Binding ElapsedTime.TotalHours,
Converter={StaticResource DoubleToIntegerConverter}, StringFormat={}{0:#00}}" />
Is there some way to avoid duplicating the whole Dataemplate but still have this one difference in the text binding of this TextBlock in the second template?
No, there is no inheritance for DataTemplate. If you think about, how would you override a part of a DataTemplate?
Solution: Use another Style to capture the common properties between the two templates. You can scope it in the same Resources block if it only place you need it. It is much cleaner or more WPF way of doing things.
I've already asked this question here once and unfortunately there isn't.
but in this specific situation you can move the fontsize,foreground,horizontalalignment..etc to a style (lets say textstyle2) that based on your current textstyle.
I got an answer to this from another post (by Liz). Basically, you can put all common controls into one DataTemplate and then create two more DataTemplates that each use the first one as a ContentTemplate in a ContentPresenter. Then, you can add different controls into one or both of the latter DataTemplates. Liz provided a code example.
<DataTemplate x:Key="UserTemplate">
<!-- show all the properties of the user class here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:User}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Author}">
<StackPanel>
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource UserTemplate}"/>
<!-- show all the additional Author properties here -->
</StackPanel>
</DataTemplate>
Thanks once again Liz.
Adding to what Dennis suggested, you can always create a custom control that you just stick inside your DataTemplate and re-style that control instead of the DataTemplate.

How to expose ItemTemplate and ContentTemplate from a UserControl

I have written a Well control, similar to the Visual Studio editor tabs so that a user can have multiple documents open and can see one or more at a time. It is derived from a UserControl and exposes an ObservableCollection of OpenDocuments that binds to the ViewModel. If I were to implement this as a simple TabControl, then this would be how it would look:
<TabControl
Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding OpenDocuments}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<vw:DocumentView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This gives me Name in the tabitem header and the DocumentView (another user control in the Content area).
My control has a ContentTemplate but it is of course representing the whole of the control so all I get to see is the DocumentView. My control doesn't have an ItemTemplate.
How do I expose an ItemTemplate and ContentTemplate?
Andrew
EDIT -------------------------------------------------------------------------
Thanks for the replies. It looks like this:
A user can have one or more document wells holding one or more tabs. All the consumer has access to is the list of visible tabs and the currently selected tabitem.
Notice that the tabs are all empty! I don't understand how to specify the ContentTemplate for the <vw:DocumentView> in the same way as the TabControl example above.
Andrew
I am a little confused on exactly what your control looks like but here is what I think you are after.
If your control can inherit from ItemsControl you will get the ItemTemplate property already defined for you. All you have to do in your control template is to Template bind the ItemsControl property to the correct place in the template.
The same applies for the ContentTemplate. If you have the a ContentTemplate property already defined for you control you just need to Template Bind it to the correct place in the control.
It would look something like this
Control Consumer
<MyControlNamespace:MyControl ContentTemplate="{StaticResource MyContentTemplate}" ItemTemplate="{StaticResource MyItemTemplate}" />
Implementation of "MyControl" from above (totally pseudo code)
<MyControl>
<ItemsPresenter ItemTemplate="{TemplateBinding ItemTemplate}" />
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" />
</MyControl>
Again, my example doesn't fully make sense but I am not sure what your control looks like and not entirely sure what you are asking, but hopefully this helps.

UserControl as Content for HeaderedContentControl.HeaderTemplate

I have a UserControl that I have successfully been using as a header for presentations that involve a list which can be headered, using the xaml below:
<DockPanel >
<uc:ListSubjectHeader Subject="{Binding DisplayName}"
AddNewItemCommand="{Binding AddCommand}"
ImageSource="..." />
<!-- other controls -->
</DockPanel>
I would like to use this same control in another presentation where it would be the content for the header in a HeaderedContentControl, and came up with this xaml to do that:
<HeaderedContentControl Content="{Binding Path=DetailViewDepartment}" >
<HeaderedContentControl.HeaderTemplate>
<DataTemplate DataType="{x:Type vm:DepartmentSelectionViewModel}">
<uc:ListSubjectHeader Subject="{Binding DisplayName}" ... />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>
</HeaderedContentControl>
The visual elements show up the way I want them to, but data does not. I should note that I am using the same view model (vm:DepartmentSelectionViewModel) in a different control's DataTemplate in the same presentation, which I asked as a different question here. If you know the answer to this one you likely know the answer to that one too.
How can I fix this?
Cheers,
Berryl
The HeaderTemplate applies to the object in the Header property, not Content. Content uses the ContentTemplate, just like in the normal ContentControl.

Resources