Trouble displaying an object in WPF - c#

I'm so new to this that I can't even phrase the question right...
Anyway, I'm trying to do something very simple and have been unable to figure it out. I have the following class:
public class Day : Control, INotifyPropertyChanged
{
public static readonly DependencyProperty DateProperty =
DependencyProperty.Register("Date", typeof(int), typeof(Day));
public int Date
{
get { return (int)GetValue(DateProperty); }
set
{
SetValue(DateProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Date"));
}
}
}
public static readonly DependencyProperty DayNameProperty =
DependencyProperty.Register("DayName", typeof(String), typeof(Day));
public String DayName
{
get { return (String)GetValue(DayNameProperty); }
set
{
SetValue(DayNameProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DayName"));
}
}
}
static Day()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Day), new FrameworkPropertyMetadata(typeof(Day)));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I've learned that you can't call a constructor that has parameters in XAML so the only way to actually set some data for this class is through the two properties, DayName and Date.
I created a ControlTemplate for Day which is as follows:
<Style TargetType="{x:Type con:Day}">
<Setter Property="MinHeight" Value="20"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="80"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type con:Day}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="2" x:Name="rectHasEntry" Fill="WhiteSmoke"/>
<TextBlock Grid.Column="0" x:Name="textBlockDayName" Text="{TemplateBinding DayName}" FontFamily="Junction" FontSize="11" Background="Transparent"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<TextBlock Grid.Column="1" x:Name="textBlockDate" Text="{TemplateBinding Date}" FontFamily="Junction" FontSize="11" Background="Transparent"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<Rectangle Grid.ColumnSpan="2" x:Name="rectMouseOver" Fill="#A2C0DA" Opacity="0"
Style="{StaticResource DayRectangleMouseOverStyle}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then render it on screen in my MainWindow thusly:
<Window x:Class="WPFControlLibrary.TestHarness.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:con="clr-namespace:WPFControlLibrary.Calendar;assembly=WPFControlLibrary"
Title="MainWindow" Height="500" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<con:Day Grid.Column="1" Height="20" Width="80" DayName="Mon" Date="1"/>
</Grid>
And what I actually see is, well, nothing. If I put my cursor on the con:Day line of the XAML it'll highlight the correctly sized rectangle in the window but I don't see "Mon" on the left side of the rectangle and "1" on the right.
What am I doing wrong? I suspect it's something simple but I'll be darned if I'm seeing it.
My ultimate goal is to group a bunch of the Day controls within a Month control, which is then contained in a Year control as I'm trying to make a long Calendar Bar that lets you navigate through the months and years, while clicking on a Day would display any information saved on that date. But I can't even get the Day part to display independent of anything else so I'm a long way from the rest of the functionality. Any help would be greatly appreciated.

First of all, you don't need to implement INotifyPropertyChanged if you have DependencyProperties. The following is more than enough:
public int Date
{
get { return (int)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
When I try your example (without rectMouseOver, since I don't have the definition of DayRectangleMouseOverStyle), Mon shows just fine but 1 does not show up. I was able to fix that by replacing TemplateBinding with an explicit binding: Instead of
{TemplateBinding Date}
use
{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Date}
After that change, your example worked fine. I don't know why, but TemplateBinding seems to be "broken" sometimes.

You need to setup a DataTemplate for your class, not a ControlTemplate.
DataTemplates are used to define how a custom class is diplayed. ControlTemplates, via Style, is used to stylize a control.
For details, I recommend reading the Data Templating Overview on MSDN.

You're approaching this from an angle that is going to give you nothing but grief. I know this because I did exactly what you're trying to do once.
Consider approaching it this way: You have a Day class that exposes DayName, DayNumber, and Column properties. You can then create a data template for that class:
<DataTemplate DataType="{x:Type local:Day}">
<TextBlock Grid.Column="{Binding Column}"
Text="{Binding DayNumber}"
ToolTip="{Binding DayName}"/>
</DataTemplate>
Now create a Week class that contains a collection of Day objects. Create a template for that class:
<DataTemplate DataType="{x:Type local:Week}">
<ItemsControl ItemSource={Binding Days}>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
And a Month class that contains a collection of Week objects. Its data template looks like this:
<DataTemplate>
<ItemsControl ItemsSource="{Binding Weeks}"/>
</DataTemplate>
(You don't need to create an ItemsPanelTemplate here because the template that ItemsPanel uses by default, a vertical StackPanel, is the one you want.
Now if you create an instance of a Month object (and populate its weeks and days correctly), anywhere in your XAML that WPF needs to render it, it will create a vertical StackPanel containing several Grids, each of which contains seven TextBlocks with the appropriate day numbers in them.
Creating a Year object and a template for it I'll leave as an exercise for you. Eventually you'll add ScrollViewers and styling to these templates, and implement more properties in the object model to help with the UI. For instance, if you want a day to display differently if it has information, you might add a HasInformation property to the Day class, and implement a data trigger to change its background color or font weight if it's true. And you'll implement Command objects for the things you actually want this to do, like display the information for a specific day. You'll get there.

Related

UWP: Highlight ListBoxItem - Events That Coincide with the Highlighting

I have a ListBox bound to a collection of items of type Definition. My requirement is that every time the mouse is hovered over the area of a templated ListBoxItem, a second ListBox open next to the ListBoxItem, revealing sub-items which are of type Word.
(I am basically implementing something similar to a TreeView using two ListBoxes. This is for earlier versions so using a TreeView control is not an option.)
This is the data structure...
public class Word
{
public string Name { get; set; }
}
public class Definition
{
public string Name { get; set }
public ObservableCollection<Word> Words;
}
public class Dictionary
{
public string Name { get; set }
public ObservableCollection<Definition> Definitions;
}
And here is the XAML view...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Height="0">
<Button.Flyout>
<Flyout x:Name="DefinitionFlyout"/>
<ListBox x:Name="WordsListBox" HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemp late x:DataType="local:Word">
<TextBox TextWrapping="NoWrap"
Height="Auto"
BorderThickness="0"
HorizontalAlignment="Stretch"
Text="{x:Bind Name}"
TextAlignment="Left"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Flyout>
</Button.Flyout>
</Button>
<ListBox x:Name="DefinitionsListBox"
Grid.Row="1"
SelectionMode="Single"
HorizontalAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="local:Definition">
<TextBox TextWrapping="NoWrap"
Height="Auto"
BorderThickness="0"
HorizontalAlignment="Stretch"
Text="{x:Bind Name, Mode=TwoWay}">
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
When the mouse pointer hovers over a Definition item in DefinitionsListBox, WordsListBox should fly out and display the Words of that Definition. And when the pointer exits that Definition and hovers over a new one, I want WordsListBox to reflect that change.
Unfortunately, I can't find the events that will help me accomplish this.
I thought defining PointerEntered and PointerExited in the TextBox of Definition would do the trick but they don't because PointerExited fires IMMEDIATELY after PointerEntered, as in almost simultaneously, and not when the mouse exits the TextBox area. And SelectionChanged of ListBox doesn't fire.
The first event should fire when ListBoxItem highlighting begins, and the second one, when the highlighting ends.
What do you recommend for this, please?
I thought defining PointerEntered and PointerExited in the TextBox of Definition would do the trick but they don't because PointerExited fires IMMEDIATELY after PointerEntered, as in almost simultaneously.
The problem is the when Flyout show at the button there is a mask layer cover on the window. This will prevent basic input event of TextBox defined. It looks PointerExited fires immediately after PointerEntered as in almost simultaneously.
For solving this , you could set OverlayInputPassThroughElement property for Flyout that make the area of ListBox could response PointerEntered PointerExited event when Flyout is opened. For more please refer the following code.
<Flyout x:Name="DefinitionFlyout" OverlayInputPassThroughElement="{x:Bind DefinitionsListBox}">

Coded UI Test Generator has trouble correctly recording actions on a ListViewItem

I'm currently working on making an application CUIT-Generator ready. That means that, as an example, I'm adding XAML setters to the styles for DataGridRow that set Automation.ID and AutomationName. Works just fine.
Now my issue is that there is a ListView where the ItemTemplate contains a DataTemplate which in turn has a custom UI control.
When recording any action on the text controls inside the custom UI control, it only grabs the custom UI control and the hierarchy below it, but it doesn't record that it is inside a ListView and a ListViewItem.
Due to this, the control can not be found during test execution or when selecting the control in the UIMap and clicking Search UI control.
I tried setting the AutomationID/Name on the ListViewItem and the ListView but that does not have an impact on the recorded hierarchy.
XAML code for the ListView:
<ListView x:Name="sampleControl" Margin="3" ItemsSource="{Binding ObservableCollectionOfViewModelItems}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="AutomationProperties.Name">
<Setter.Value>
<Binding Path="AutoID"/>
</Setter.Value>
</Setter>
<Setter Property="AutomationProperties.AutomationId">
<Setter.Value>
<Binding Path="AutoID"/>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<SampleNamespace:CustomUIControlView />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code for the CustomUIControlView:
<UserControl x:Class="SampleNamespace.CustomUIControlView"
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">
<Border BorderThickness="3">
<Expander>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Name="SampleBox1" Height="20" Margin="5" Text="{Binding SampleProp1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="1" Name="SampleBox2" Height="20" Margin="5" Text="{Binding SampleProp2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="2" Name="SampleBox3" Height="20" Margin="5" Text="{Binding SampleProp3, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="7" Name="SampleBox4" Height="20" Margin="5" Text="{Binding SampleProp4, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Expander>
</Border>
</UserControl>
The recorded hierarchy for an action recorded on SampleBox1 looks like this:
SampleAppWindow
---- CustomUIControlView
-------- Expander
------------ SampleBox1/TextBox
Obviously none of the controls, aside from the Window are going to be found.
I know that I could transfer the recorded actions to C# code and then edit the hierarchy and search properties myself but I would like to avoid doing this as I would have to remember doing that every time a ListView is involved in a recorded test.
I'm mentioning this as most solutions here on SO or on other websites come down to working around the problem like that.
This is on Visual Studio 2017 15.3.5 and .NET Framework 4.5.2.
I'm fairly certain you are asking for something beyond what the CodedUi Code Generator can handle.
I would point out from the generated object structure it missed entirely the ListView. The route I would recommend is first being sure you can locate the ListView, generated or your own code, probably call DrawHighlight to be sure. I get your trying to avoid your own code for defining object definitions but it is probably only feasible to write your own.
Now specifically for your CustomUIControlView, I would urge you to define a matching CodedUI object that matches it. As is, an Expander with 4 children TextBoxes. This would cut down coding this definition several times. If you look at the generated code as examples to write these.
Depending on your view on designer files, you could also declare the immediately expected parent node of your ListView or itself as a partial class to hook in these unmatched children elements in a separate file to avoid it getting wiped by the generator. Then you would only need to update the designer file with the small partial statement edits.

Dynamic width column xaml won't work

I have been trying to get dynamic column width to work for my simple WP8.1 app. The goal is to have the first column take up half of the listbox, and the other two columns a quarter each. I hoped to do this by assigning a dynamic width, using the * indicator as described here.
This let me to the piece of xaml code at the bottom of my post.
In my MainPage I set the DataContext to an ObservableCollection, and all the data shows up in their respective columns, the columns just do not get the desired width (they are all as small as can be).
What prevents my dynamic width from working? I have toyed around with HorizontalAlignment and Width of TextBoxes too, but with no success. I tried to look around for answers, and I even used some examples that did work, but that did not bring me closer to understanding why it does not work here.
Thanks in advance.
<Grid>
<ListBox Name="transactionListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Category}"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Amount}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The item Grid itself is being sized-to-fit by the ListBox. You can override this behavior by setting the ItemContainerStyle to make the ListBoxItems stretch to fill horizontally:
<ListBox Name="transactionListBox" ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
...
</ListBox>
I think you need a constraining width on the ListBox or Grid. With your code, the grid and ListBox will re-size to fit the content, so there is no need for the dynamic column definitions.

Grid alternate color

I am trying to display a Grid with alternating color each other row. The best would be alternating template all together to be able to change the whole row.
To make sure there is no misunderstanding when i mean grid i mean grid, not datagrid, not gridview, grid as in <Grid></Grid>
Right now the only viable solution i have found was to make a grid with the appropriate amount of rows i want and in each rows i put another grid with 1 row and the 3 columns i need and copy pasted for each row by changing the back color. As you can see this is not very clean solution.
So i have looked around and found that the listbox can have the alternate count and on a simple trigger it can change everything and that was perfect until i noticed the highlight CANNOT be disabled. You can change the highlight brush but it override an alternate color so both cannot be used at the same time. Before you asked yes i did use the transparent bush and transparent is NOT really transparent, All it does is that it shows the color underneath the ListBox control which was the beige color of the canvas underneath and the item itself disappear.
Anyone know a way to apply a template of alternating row on a grid. Putting a simple style on the RowDefinition would be easy but since you can't really tell the type of element you put in a grid i doubt there is something that can be done as easy as that.
EDIT:
here my latest change. a little bit "cleaner"
i created 2 style for item control for each color theme like
<Style x:Key="PropertyGrid" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="AlternatePropertyGrid" TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid Background="#FFEBEBEB">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Then i used a stack panel and listed each line
<StackPanel>
<ItemsControl Style="{StaticResource PropertyGrid}">
<Label Content="Identifier" Style="{StaticResource PropertyNameLabel}"/>
<Label Content="{Binding ElementName=cboActions, Path=SelectedItem.UniqueIdentifier}" Style="{StaticResource PropertyValueLabel}"/>
<Rectangle Style="{StaticResource PropertyGridSplitter}"/>
</ItemsControl>
<ItemsControl Style="{StaticResource AlternatePropertyGrid}">
<Label Content="Source" Style="{StaticResource AlternatePropertyNameLabel}"/>
<Label Name="lblSource" Style="{StaticResource AlternatePropertyValueLabel}"/>
<Rectangle Style="{StaticResource AlternatePropertyGridSplitter}"/>
</ItemsControl>
<ItemsControl Style="{StaticResource PropertyGrid}">
<Label Content="Description" Style="{StaticResource PropertyNameLabel}"/>
<Label Name="lblActionDescription" Style="{StaticResource PropertyValueLabel}"/>
<Rectangle Style="{StaticResource PropertyGridSplitter}"/>
</ItemsControl>
</StackPanel>
each item in their respective style they have the grid column preset so style determine the location in the grid of each object within the item control.
It works but still i feel like there is probably a more efficient way to do it. here's a screenshot of what it look like right now, might be more helpful for the visual people to know what i am trying to achieve here.
With a Datagrid, you can easily do that :
<DataGrid AlternatingRowBackground="Blue"/>
With a grid, no idea :p
For a listbox inside the Grid, we set alternate colors with a some lines of code (code-behind).
private void SetAlternateColor()
{
var blueBrush = new SolidColorBrush(Colors.Blue);
var redBrush = new SolidColorBrush(Colors.Red);
for (int i = 0; i < Items.Count; i++)
{
ListBoxItem item = TestListBox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
item.Background = i % 2 == 0 ? blueBrush : redBrush;
}
}
private void TestGrid_Loaded(object sender, RoutedEventArgs e)
{
SetAlternateColor();
}
Don't know if this is what you are looking for.
Repeating the code was fastest solution.

Binding command to a templated user control

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" />
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.

Categories