DRY - Subclassing WPF windows - wpf

I have several WPF windows which will be very similar except for the columns on a DataGrid (the DataContext will be ObservableCollections of different objects), the text in some Labels and a Button click handler.
For each window, the <DataGrid.Columns> part of the DataGrid is different. It uses AutoGenerateColumns="False" and shows different columns for the different objects.
I wonder if it's possible to subclass a base WPF window so I can just write the <DataGrid.Columns> part on the XAML for each subclass instead of writing it in code.
Or what other techniques exist for abiding by the DRY principle on WPF while still using XAML?

How do I populate DataGrid Columns from the datasource...
Yes, you have come across a limitation here. The Columns property is not bindable; in fact it isn't even settable, you can only add and remove from the collection. There is a workaround at this question: How do I bind a WPF DataGrid to a variable number of columns?
So theoretically, you could add the columns to <Application.Resources>, then databind an attached property as in the question above, and write a value converter that builds a column collection based on the datasource value, pulling from Application.Current.Resources. But this seems more convoluted than it needs to be.
I think you could just use a style trigger that replaces some Content with different DataGrids:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... default columns go here ...
</DataGrid.Columns>
</DataGrid>
<Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeCondition}" Value="True">
<Setter Property="Content">
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... alternate columns ...
</DataGrid.Columns>
</DataGrid>
</Setter>
</DataTrigger>
... additional triggers as needed ...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
This could be part of a larger common view template -- no need to create separate view classes.

I would do it with one window and different DataTemplates. However, if you want to use inheritance then you could override the DataTemplate in the Window.Resources using the key that is referenced by the base Window. The DataTemplate would have the Xaml for the whole datagrid.

Related

Binding in Style Setter in DataTemplate WPF

Okay, I have a relatively involved problem. I'm trying to create a Window in WPF. The main element on this window is a DataGrid. Each one of the rows in the DataGrid has a DetailsPane which I set using DataGrid.RowDetailsTemplate. Depending on certain row-specific values, I need the DetailsPane to display different elements. To accomplish this I placed a ContentControl at the root of the DataTemplate and used a Style with DataTriggers to set its Content property. Now, inside one of these Setters is a ComboBox. This ComboBox needs to have its ItemsSource bound to a list, which is stored in a dependency property on the Window level (because its the same list regardless of the row). Below is a simplified version of what I'm looking at:
<Window>
...
<DataGrid>
...
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ContentControl>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RowSpecificBooleanProperty}" Value="False">
<Setter Property="Content">
<Setter.Value>
...
<ComboBox ItemsSource={HowDoIBindThisToTheWindowProperty}/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Window>
So what I'm trying to figure out is how to bind the ItemsSource of that ComboBox to a dependency property of the top-level window. Andy idea how to accomplish that?
EDIT:
I should have mentioned this before but I've already tried using {RelativeSource AncestorType=Window} and ElementName in the binding. In both cases the list in the ComboBox is blank at runtime.
ItemsSource="{Binding WhateverList, RelativeSource={RelativeSource AncestorType=Window}}"

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.

How to apply convention-over-configuration with Caliburn.Micro instead of these DataTriggers?

I have a View/ViewModel pair properly working with Caliburn.Micro.
Inside the view, there is a ContentControl whose content is bound to the same viewmodel, and depending on the value of a given enumerated property in the viewmodel, I want a different template for the ContentControl:
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate x:Key="TurnedOffView">
<local:TurnedOffView/>
</DataTemplate>
<DataTemplate x:Key="DeviceReadyView">
<local:DeviceReadyView/>
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource TurnedOffView}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Static local:DeviceStates.Ready}">
<Setter Property="ContentTemplate" Value="{StaticResource DeviceReadyView}"/>
</DataTrigger>
<!-- More DataTriggers here, one for each state -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I know Caliburn.Micro can use cal:View.Model and cal:View.Context so that multiple views can be bound to each ViewModel, but I can't figure out how to use this to avoid all this verbosity.
So the question is:
How should I use View.Model and View.Context (and rename my views) in this scenario to take advantage of Caliburn.Micro convention-over-configuration approach?
<ContentControl cm:View.Model="{Binding}" cm:View.Context="{Binding ContextProp, Mode=TwoWay}" /> might be something to try... Then your context would be based off code in your viewmodel not having to structure datatemplates with view-first code. ContextProp would be basically a sub view of your view in question (based on namespace Project.Views.MainView.Main.TurnedOff) ContextProp = "TurnedOff";
yea sorry.. Its how I see the folder structure (namespaces)... So instead of TurnedOffView it could be TurnedOff.xaml under a folder named Main. This was assuming your primary view is MainView.xaml and your primary Viewmodel is MainViewModel.cs. SOrry for the confusion.
View.Model can be setup to do multiple viewmodels, at that rate you might want to think about Conductor in the framework. But I don't think it would be warranted since you are basically doing view switching in the presented case above.

Swapping between WPF DataTemplates

I have a WPF application where I have a small summary view for data but the user is able to expand that view up to a larger display. The data content is basically the same so I was considering using the same view model for both. Right now using MVVM the display of the data is governed by a DataTemplate.
How do I swap between Datatemplates for the same view model? Is it even possible or do I need to create a ViewModel for the small view and expanded view?
Use a ContentPresenter inside the "Primary" (notice the quotes) DataTemplate, and some DataTriggers to dynamically change it's ContentTemplate:
<DataTemplate>
<ContentPresenter x:Name="Content"
Content="{Binding}"
ContentTemplate="StaticResource myViewModel1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding SomeVMProperty}" Value="SomeValue">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource myViewModel2}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
This way, when the DataTrigger is triggered, the ContentTemplate will be myViewModel2, and when it's not, it will be myViewModel1.

How and where dispose a ViewModel?

MVVM pattern is wonderful with MVVM Light better but sometimes I think I don't understand anything.
I've a business application in SL 4 where I've, by now, 18 VM.. and more to write! I'm applying the pattern Laurent Bugnion used in his session at MIX11 (with SimpleIoc class).
A viewmodel is bind to a view (name it "A") but the very same viewmodel is bind to another view too (name it "B"). Viewmodel bound with view "A" is called in a standard way in the ViewModelLocator. Viewmodel bound with view B is called with a different key so to be sure they are 2 different istances. Besides they are injected with different DomainService so entities bind with controls on view are different.
The view model registers for some messages to keep track of the variation in other viewmodels it interacts, say a selection changed means the user wants to display other things in order to retrive data on DB).
The problem is if I call view A and then view B I register for the same messages 2 times so I've 2 operations on DB.
What I think correct is to dispose viewmodel bound to view A when I call view B (generally when I close view A). But I don't really know where to dispose it, when and how! Ok.. I could imagine when and how.. but WHERE?
If you're thinking I'm confused, you're right!
If I understand you correctly, you're using the same ViewModel with two different Views. You want only one copy of the VM existing at a time.
In this case, I'd probably use whatever the VM's parent is and modify something like a Mode property on the VM.
<DataTemplate x:Key="ViewA" TargetType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm View A" />
</DataTemplate>
<DataTemplate x:Key="ViewB" TargetType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm View B" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Then to switch views, I would simply set ParentViewModel.CurrentViewModel.Mode = 2 and the View simply gets switched without changing the ViewModel.
If you want two different copies of the same ViewModel, I'd still handle the switching in the ParentViewModel using something like ParentViewModel.CurrentViewModel = ViewModelInstanceB, and have the ViewModelInstanceB.Mode set to 2
I wrote some examples of switching between Views here if you're interested

Resources