Reputation: 11
I'm new to WPF, but have been able make a lot of progress in short time thanks to a good book on the topic, and of course, quality posts on sites like this one. However, now I've come across something I can seem to figure out by those means, so I posting my first question.
I've have a ControlTemplate in a resource dictionary which I apply to several UserControl views. The template provides a simple overlay border and two buttons: Save and Cancel. The templated user control holds various text boxes, etc., and is bound to some ViewModel depending on the context. I'm trying to figure out how to bind the commands to the Save/Cancel buttons when I use/declare the UserControl in some view. Is this is even possible, or am I doing something very wrong?
First, the template:
<ControlTemplate x:Key="OverlayEditorDialog"
TargetType="ContentControl">
<Grid>
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="DarkGray"
Opacity=".7"/>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="DarkGray">
<Grid>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
<Grid Grid.Row="1"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Content="Cancel"
***Command="{Binding CancelCommand}}"**
/>
<Button Grid.Column="0"
Content="Save"
***Command="{Binding Path=SaveCommand}"***/>
</Grid>
</Grid>
</Border>
</Grid>
</ControlTemplate>
The template in turn is used in the CustomerEditorOverlay user control
<UserControl x:Class="GarazhApp.View.CustomerEditorOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<UserControl.Resources>
<ResourceDictionary Source="Dictionary1.xaml"/>
</UserControl.Resources>
<ContentControl Template="{StaticResource ResourceKey=OverlayEditorDialog}">
<Grid Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<SomeElement/>
<SomeOtherElement/>
</Grid>
</ContentControl>
...and finally, the user control is used as part of a view like so:
<local:CustomerEditorOverlay Visibility="{Binding Path=CustomerViewModel.ViewMode, Converter={StaticResource myConverter}, FallbackValue=Collapsed}"
d:IsHidden="True" />
Upvotes: 1
Views: 197
Reputation: 2119
So, based on what I've learned from a project I have been on forever and a half, we have a workable pattern.
Let's say you have a bunch of modal windows that all get applied the same style within the application. To have Save and Cancel buttons on each view, the UserControl used for all of the modal windows has several dependency properties. In addition, we specify virtual methods for your commands (e.g. OnSaveCommand, OnCancelCommand, CanExecuteSaveCommand, CanExecuteCancelCommand) and the commands themselves as properties in a base ViewModel that is inherited by your views.
Ultimately, what happens is we create new modal windows by simply doing this:
<my:YourBaseView x:class="MyFirstView" xmlns:whatever="whatever" [...]>
<my:YourBaseView.PrimaryButton>
<Button Content="Save" Command="{Binding SaveCommand}" />
</my:YourBaseView.PrimaryButton>
<!-- some content -->
</my:YourBaseView>
With accompanying code-behind:
public class MyFirstView : YourBaseView
{
[Import] /* using MEF, but you can also do MvvmLight or whatever */
public MyFirstViewModel ViewModel { /* based on datacontext */ }
}
And a ViewModel:
public class MyFirstViewModel : ViewModelBase
{
public override OnSaveCommand(object commandParameter)
{
/* do something on save */
}
}
The template for this UserControl specifies ContentControls in a grid layout with the Content property bound to the PrimaryButton and SecondaryButton. Of course, the content for the modal is stored in the Content property of the UserControl and displayed in a ContentPresenter as well.
<Style TargetType="{x:Type my:YourBaseView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:YourBaseView}">
<Grid>
<!-- ignoring layout stuff -->
<ContentControl Content="{TemplateBinding Content}" />
<ContentControl Content="{TemplateBinding PrimaryButton}" />
<ContentControl Content="{TemplateBinding SecondaryButton}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UserControl code:
public class YourBaseView : UserControl
{
public static readonly DependencyProperty PrimaryButtonProperty =
DependencyProperty.Register("PrimaryButton", typeof(Button), typeof(YourBaseView), new PropertyMetadata(null));
public Button PrimaryButton
{
get { return (Button)GetValue(PrimaryButtonProperty); }
set { SetValue(PrimaryButtonProperty, value); }
}
/* and so on */
}
You can change the style for each instance of your templated view, of course. We just happen to stick with one base style.
TL;DR edit: I may have gone a bit overboard since I think you just need the understanding that exposing dependency properties of type Button which are set up through the XAML each time you create a new overlay. That, or you could probably RelativeSource your way back up to the visual tree with something like {Binding DataContext.SaveCommand, RelativeSource={RelativeSource AncestorType={x:Type MyView}}}
but it's a little dirtier.
Upvotes: 4