Class Dependencies WPF Control - wpf

WPF is really amazing for many reasons and one of them is that it allows us to change the controls inside a control. As example if we take a ListBox. We can change the panel from StackPanel to WrapPanel and it will still work (I guess its obviously that ListBox and WrapPanel don't share any class dependencies hence why it works).
Here is an example what I mean with class dependencies.
public class Test1
{
public Test2 t;
public Test1(Test2 t)
{
this.t = t;
}
}
public class Test2
{
public string someStr;
}
Now the instance could be injected/inserting like this:
Test2 test2 = new Test2();
test2.someStr = "Hello";
Test1 test1 = new Test1();
test1.t = test2;
Or instance could be inserted like this:
Test2 test2 = new Test2();
test2.someStr = "Hello";
Test1 test1 = new Test1(test2);
There are few other ways to do this but I hope that you guys now get the point.
So now after we know how to inject the instance lets try to do so in WPF with this following example:
I have a CustomControl. My CustomControl is a little bit complex because it has rows and columns. Futhermore the CustomControl is not derivering from a DataGrid. In fact it is derivering from an ItemsControl.
As I mentioned it has columns and rows or to be more precise the rows need to know about the columns. Thats where I would like to insert the instance of columns in a row. How do i do that in WPF?
Here is a simple plain example of my problem:
Lets say the VisualTree of the CustomControl looks like this:
CustomControl
+ Grid
+ Border
+ ContentPresenter
+ Headers
+ DockPanel
+ Border
+ ContentPresenter
+ Rows
As you can see the Rows are far away from Headers and I would like to get/insert the instance of Headers to the Rows without finding the Headers.
Rows and Headers classes look like this:
class Row : ContentControl
{
List<Column> Headers;
...
}
class Headers : ContentControl
{
List<Column> Cols = new List<Column>()
public Headers()
{
this.Cols.Add(...);
this.Cols.Add(...);
}
...
}
And the problem is how to do something like this:
this.rows.Headers = columns.Cols;
I have searched on the internet and many people have suggested me to let the row use VisualTreeHelper and then to travel up the tree to find the Cols. In fact I tried to do so and after monitoring my CustomControl with a Performance Profiler Tool, I figured that it's exactly the step where every row stumbles up the tree to find header that takes the most of the time. Therefore lets not use VisualTreeHelper and lets use injection like I described in example with Test1 and Test2 above.
Any suggestions? Is there maybe a pattern for this?
EDITED: #Benjamin. To use RedSlider = GetTemplateChild("RedSlider") inside the OnApplyTemplate is a great solution but it doesnt work for me because my case is more complex. In my case i really need to insert the instance somehow. Here is an example of my case where I cannot use GetTemplateChild inside the OnAppyTemplate method.
This is Control for Sliders.
class CustomSliders : ContentControl
{
}
The style for CustomSlider looks like this:
<Style x:Key="mySliders" TargetType="{x:Type local:CustomSliders}">
<Style.Setter Property="Template">
<ControlTemplate TargetType="{x:Type local:CustomSliders}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Slider x:Name="PART_RedSLider" />
<Slider x:Name="PART_GreenSlider" Grid.Row="1"/>
<Slider x:Name="PART_BlueSlider" Grid.Row="2"/>
<Slider x:Name="PART_AlphaSlider" Grid.Row="3"/>
</Grid>
</ContentTemplate>
</Style>
And this is the ControlTemplate of my CustomControl called ColorPicker.
<ContentTemplate TargetType="Picker">
<DockPanel>
<ContentControl DockPanel.Dock="Left" Style="{StaticResource mySliders}"/>
<Rectangle x:Name="PART_ColorPresenter"
DockPanel.Dock="Right"
Margin="5"/>
</Grid>
</ControlTemplate>
In this example the GetTemplatedChild method which will be executed inside OnApplyTemplate of ColorPicker wont work because it cannot find the PART_RedSLider which is inside CustomSliders.
GetTemplateChild cannot find everything down the VisualTree.
Because GetTemplatedChild wont work to solve this complex template structure many people suggest me to travel recrusivly up or down the tree by using the VisualTreeHelper.Find(). Though like I explained in the question I dont want to use the recrusive way. I want to insert the instance..

As I have already stated, Kent Boogaarts answer is technically correct but I think you probably require more information to aid your understanding.
Imagine the control you are writing is a colour picker. The control has 4 Sliders for changing the red, green, blue and alpha values and the end result is displayed beside the sliders in a Rectangle. A mock-up is shown below:
This control has 5 dependencies in order for it to work correctly; 4 Sliders and 1 FrameworkElement for displaying the end result.
What this means is that in my ControlTemplate I am expecting to see 4 sliders and 1 FrameworkElement that are named. The slider that controls the red value, for example, could be called "PART_RedSlider" (the name can be anything you want but the recommended approach is to prefix the name with "PART_").
An example template may look something like this:
<!-- Typically this would be defined in a style -->
<ControlTemplate TargetType="ColorPicker">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Slider x:Name="PART_RedSLider" />
<Slider x:Name="PART_GreenSlider" Grid.Row="1"/>
<Slider x:Name="PART_BlueSlider" Grid.Row="2"/>
<Slider x:Name="PART_AlphaSlider" Grid.Row="3"/>
<Rectangle x:Name="PART_ColorPresenter"
Grid.Column="1"
Grid.RowSpan="4"
Margin="5"/>
</Grid>
</ControlTemplate>
Now in your controls code you would override the OnApplyTemplate() method, like so:
public override void OnApplyTemplate()
{
RedSlider = GetTemplateChild("PART_RedSlider") as Slider;
GreenSlider = GetTemplateChild("PART_GreenSlider") as Slider;
BlueSlider = GetTemplateChild("PART_BlueSlider") as Slider;
AplhaSlider = GetTemplateChild("PART_AlphaSlider") as Slider;
ColorPresenter = GetTemplateChild("PART_ColorPresenter") as FrameworkElement;
}
You now have access to the named parts of your ControlTemplate. You can then do whatever you want to these controls in your code (set default property values, hookup event handlers, etc).
Now you may be wondering where the TemplatePartAttribute comes into all this. The thing is, this attribute is purely for documentation purposes so that tools (such as Expression Blend) can assist other people creating a custom template.
The attribute is applied to the class you are writing like so:
[TemplatePart(Name="PART_RedSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_GreenSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_BlueSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_AlphaSlider", Type=typeof(Slider)]
[TemplatePart(Name="PART_ColorPresenter", Type=typeof(FrameworkElement)]
public class ColorPicker
A piece of advice is to never assume that the template will include a named part. Your control should still function (to the best of it's ability) without the named part being present. The guidance from Microsoft is that you should not throw exceptions if a named part is missing (I.E. your application can still run even though your control will not work as expected).
E.G.
public override void OnApplyTemplate()
{
RedSlider = GetTemplateChild("RedSlider") as Slider;
if (RedSlider != null)
{
// Only do anything if the named part is present.
}
}
You should also try to use the lowest base class possible for named parts. E.G. in my example, the named parts for the Sliders could be of type RangeBase rather than Slider so that of types of range controls can be used.
Finally, your class should have variables that hold references to your named parts which are retrieved during the call to OnApplyTemplate. Do not try and find the controls each time you want to use them.
So now you need to apply this to your control which is dependant on the Row and Header controls so these need to be present in your ControlTemplate (I will assume a single Row and Header for simplicity).
First of all document your CustomControl with the TemplatePartAttribute like so:
[TemplatePart(Name="PART_Header", Type=typeof(Header)]
[TemplatePart(Name="PART_Row", Type=typeof(Row)]
public class CustomControl : Control
{
// etc...
Now make sure you have these controls as named parts in your ControlTemplate like so:
<ControlTemplate TargetType="CustomControl">
<Grid>
<!-- other elements omitted -->
<Header x:Name="PART_Header" />
<Row x:Name="PART_Row" />
</Grid>
</ControlTemplate>
And then you use the OnApplyTemplate() method like this:
public override void OnApplyTemplate()
{
myHeader = GetTemplateChild("PART_Header") as Header;
myRow = GetTemplateChild("PART_Row") as Row;
if ((myHeader != null) && (myRow != null))
{
myRow.Headers = myHeader.Cols;
}
}

If your custom control has specific requirements regarding its visual tree, you can declare these using TemplatePartAttributes and use resolve them during OnApplyTemplate.

Related

WPF binding to a number of objects only known at runtime

I'm trying to understand the limits of binding in WPF (if any). I understand how binding to a pre-defined number of objects in XAML works, e.g.:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyText}" Grid.Row="1" Grid.Column="0"/>
</Grid>
(I used TextBlock just as an example, it could have been a Button or any other element)
Now, suppose that, instead of a single TextBlock, I need to display a number of them, but the exact number will only be known at run-time, together with the text to be written in each TextBlock (and possibly other attributes I may want to bind). Is this something that can be achieved in practice?
To display multiple items in WPF, you would typically use the base ItemsControl class or one of the classes that derives from it. Here is a diagram of the inheritance hierarchy, you can use ItemsControl when all you need is basic functionality and one of its derived classes when you need more:
ItemsControl and its children provide an ItemsSource attribute that allows you to bind your collection (usually an ObservableCollection). However, for user-defined types, you will also need to provide a data template to tell the control how to display the contents.
For example, say you had a simple class like the following:
public class Message
{
public string MyText { get; set; }
}
And you create a list of them (in your case you would populate the list at run time):
Messages = new List<Message>
{
new Message { MyText = "SomeText1" },
new Message { MyText = "SomeText2" },
new Message { MyText = "SomeText3" },
};
You could display them all using the following xaml:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyText}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In side the DataTemplate you would add the controls you use to display the properties of your type and bind to them.
NOTES
Please note that the example above is the bare bones implementation just to show how to get started. Once you get more advanced, you may need to implement change notifications for your properties (i.e. INotifyPropertyChanged) and also for adding/removing items for your collection, etc.

WPF control alignment is wrong when dynamically added

Hi I thought I could solve this easily but it is driving me crazy.
I am using a UserControl to house a video player control based on VLC, along with play and stop buttons etc. I then place the UserControl on my main form. if the UserControl is declared in XAML it behaves normally.
I decided to rewrite the code to instantiate my UserControl dynamically, in case I need to destroy it and create another on the fly. But when I do the video moves to the top of its container instead of the middle.
The UserControl relevant section is here:
<Grid x:Name="LayoutParent" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<!-- I comment this if adding player dynamically -->
<!--<wpf:VlcPlayer Grid.Row="0" x:Name="Player" />-->
<!-- I un-comment this if adding player dynamically -->
<Grid x:Name="VideoPlayerPanel" Grid.Row="0" Margin="0" />
<StackPanel Grid.Row="1" Opacity="0.8">
...(buttons etc)
</StackPanel>
<ProgressBar ...(progressBar etc) />
</Grid>
My codebehind looks like this:
Dim Player As VlcPlayer = New VlcPlayer ' uncomment If adding the player dynamically
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Player.SetValue(Canvas.ZIndexProperty, -1)
VideoPlayerPanel.Children.Add(Player)
VolumeSlider.Value = 50
End Sub
I have tried VerticalAlignment="Center" and VerticalAlignment="Stretch" in XAML on the VideoPlayerPanel, with Center the video disappears entirely, with Stretch it still aligns to the top.
Any thoughts as to what I might do to align this centrally would be much appreciated!
When adding Player dynamiccaly you have different result, because you wrap Play in additional Grid. Try to add Player directly to first row of LayoutParent:
Player.SetValue(Grid.Row, 0)
LayoutParent.Children.Add(Player)
Thanks to all that replied.
I did some more research, I substituted in a Rectangle for the player control and it aligned perfectly. That led me to discover that the third party control was itself at fault. I had to get the source and change the VerticalAlignment directly.
Sorry for the runaround.
Remove Height="*" from first Row . * is used to occupy remaining space, so it is good to use it for the last Row.
Use fixed width and or Auto.

How to build a grid of controls in XAML?

I am trying to build a UI in WPF to a specification. The UI is for editing a collection of items. Each item has an editable string property, and also a variable number of read-only strings which the UI needs to display. It might look something like this:
or, depending on data, might have a different number of text label columns:
The number of text columns is completely variable and can vary from one to "lots". The specification calls for the columns to be sized to fit the longest entry (they are invariably very short), and the whole thing should look like a grid. This grid will be contained in a window, stretching the text box horizontally to fit the window.
Importantly, the text boxes can contain multi-line text and will grow automatically to fit the text. The rows below need to be pushed out of the way if that happens.
Question: what would be a good way of doing this in WPF?
Coming from a WinForms background, I am thinking of a TableLayoutPanel, which gets populated directly by code I write. However, I need to do this in WPF. While I could still just get myself a Grid and populate it in code, I would really rather prefer a way that's more in line with how things are done in WPF: namely, define a ViewModel, populate it, and then describe the View entirely in XAML. However, I can't think of a way of describing such a view in XAML.
The closest I can get to this using MVVM and XAML is to use an ItemsControl with one item per row, and use a data template which, in turn, uses another ItemsControl (stacked horizontally this time) for the variable number of labels, followed by the text box. Unfortunately, this can't be made to align vertically in a grid pattern like the spec requires.
This does not map all too well, you could probably use a DataGrid and retemplate it to look like this. In other approaches you may need to imperatively add columns or the like to get the layout done right.
(You can hook into AutoGeneratingColumn to set the width of that one writeable column to *)
You can create your own Panel and then decide on how you want the layout logic to work for the children that are put inside it.
Look at this for inspiration:
http://www.nbdtech.com/Blog/archive/2010/07/27/easy-form-layout-in-wpf-part-1-ndash-introducing-formpanel.aspx
You could have a "ColumnCount" property, and then use that within the MeassureOverride and ArrangeOverride to decide when to wrap a child.
Or you could modify this bit of code (I know it's Silverlight code, but it should be close to the same in WPF).
http://blogs.planetsoftware.com.au/paul/archive/2010/04/30/autogrid-ndash-part-1.aspx
Instead of having the same width for all columns (the default is 1-star "*"), you could add a List/Collection property that records the different column widths sized you want, then in the AutoGrid_LayoutUpdated use those widths to make the ColumnDefinition values.
You've asked for quite a bit, the following code shows how to build a grid with the controls you want that sizes as needed, along with setting up the bindings:
public void BuildListTemplate(IEnumerable<Class1> myData, int numLabelCols)
{
var myGrid = new Grid();
for (int i = 0; i < myData.Count(); i++)
{
myGrid.RowDefinitions.Add(new RowDefinition() { Height= new GridLength(0, GridUnitType.Auto)});
}
for (int i = 0; i < numLabelCols; i++)
{
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) });
}
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
for (int i = 0; i < myData.Count(); i++)
{
for (int j = 0; j < numLabelCols; j++)
{
var tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding("[" + i + "].labels[" + j + "]"));
tb.SetValue(Grid.RowProperty, i);
tb.SetValue(Grid.ColumnProperty, j);
tb.Margin = new Thickness(0, 0, 20, 0);
myGrid.Children.Add(tb);
}
var edit = new TextBox();
edit.SetBinding(TextBox.TextProperty, new Binding("[" + i + "].MyEditString"));
edit.SetValue(Grid.RowProperty, i);
edit.SetValue(Grid.ColumnProperty, numLabelCols);
edit.AcceptsReturn = true;
edit.TextWrapping = TextWrapping.Wrap;
edit.Margin = new Thickness(0, 0, 20, 6);
myGrid.Children.Add(edit);
}
contentPresenter1.Content = myGrid;
}
A Quick Explanation of the above All it is doing is creating the grid, defines rows for the grid; and a series of columns for the grid that auto size for the content.
Then it simply generates controls for each data point, sets the binding path, and assigns various other display attributes along with setting the correct row/column for the control.
Finally it puts the grid in a contentPresenter that has been defined in the window xaml in order to show it.
Now all you need do is create a class with the following properties and set the data context of the contentPresenter1 to a list of that object:
public class Class1
{
public string[] labels { get; set; }
public string MyEditString { get; set; }
}
just for completeness here is the window xaml and constructor to show hooking it all up:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ContentPresenter Name="contentPresenter1"></ContentPresenter>
</Window>
public MainWindow()
{
InitializeComponent();
var data = new List<Class1>();
data.Add(new Class1() { labels = new string[] {"the first", "the second", "the third"}, MyEditString = "starting text"});
data.Add(new Class1() { labels = new string[] { "col a", "col b" }, MyEditString = "<Nothing>" });
BuildListTemplate(data, 3);
DataContext = data;
}
You can of course use other methods such as a listview and build a gridview for it (I'd do this if you have large numbers of rows), or some other such control, but given your specific layout requirements probably you are going to want this method with a grid.
EDIT: Just spotted that you're looking for a way of doing in xaml - tbh all I can say is that I don't think that with the features you're wanting that it is too viable. If you didn't need to keep things aligned to dynamically sized content on seperate rows it would be more viable... But I will also say, don't fear code behind, it has it's place when creating the ui.
Doing it in the code-behind is really not a WPFish(wpf way).
Here I offer you my solution, which looks nice imo.
0) Before starting, you need GridHelpers. Those make sure you can have dynamically changing rows/columns. You can find it with a little bit of google:
How can I dynamically add a RowDefinition to a Grid in an ItemsPanelTemplate?
Before actually implementing something, you need to restructure your program a little. You need new structure "CustomCollection", which will have:
RowCount - how many rows are there(implement using INotifyPropertyChanged)
ColumnCount - how many columns are there(implement using INotifyPropertyChanged)
ActualItems - Your own collection of "rows/items"(ObservableCollection)
1) Start by creating an ItemsControl that holds Grid. Make sure Grid RowDefinitions/ColumnDefinitions are dynamic. Apply ItemContainerStyle.
<ItemsControl
ItemsSource="{Binding Collection.ActualItems,
Converter={StaticResource presentationConverter}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
local:GridHelpers.RowCount="{Binding Collection.RowCount}"
local:GridHelpers.StarColumns="{Binding Collection.ColumnCount,
Converter={StaticResource subtractOneConverter}"
local:GridHelpers.ColumnCount="{Binding Collection.ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The only thing left to do: implement presentationConverter which converts your Viewmodel presentation to View presentation. (Read: http://wpftutorial.net/ValueConverters.html)
The converter should give back a collection of items where each "label" or "textbox" is a seperate entity. Each entity should have RowIndex and ColumnIndex.
Here is entity class:
public class SingleEntity
{
..RowIndex property..
..ColumnIndex property..
..ContentProperty.. <-- This will either hold label string or TextBox binded property.
..ContentType..
}
Note that ContentType is an enum which you will bind against in ItemsTemplate to decide if you should create TextBox or Label.
This might seem like a quite lengthy solution, but it actually is nice for few reasons:
The ViewModel does not have any idea what is going on. This is purely View problem.
Everything is dynamic. As soon you add/or remove something in ViewModel(assuming everything is properly implemented), your ItemsControl will retrigger the Converter and bind again. If this is not the case, you can set ActualItems=null and then back.
If you have any questions, let me know.
Well, the simple yet not not very advanced way would be to fill the UI dynamically in the code-behind. This seems to be the easiest solution, and it more or less matches your winforms experience.
If you want to do it in a MVVM way, you should perhaps use ItemsControl, set the collection of items as its ItemsSource, and define a DataTemplate for your collection item type.
I would have the DataTemplate with something like that:
<Window x:Class="SharedSG.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:SharedSG"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type app:LabelVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="G1"/>
<ColumnDefinition SharedSizeGroup="G2"/>
<ColumnDefinition MinWidth="40" Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding L1}" Grid.Column="0"/>
<Label Content="{Binding L2}" Grid.Column="1"/>
<TextBox Grid.Column="2"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Grid.IsSharedSizeScope="True">
<ItemsControl ItemsSource="{Binding}"/>
</Grid>
</Window>
You're probably way past this issue, but I had a similar issue recently and I got it to work surprisingly well in xaml, so I thought I'd share my solution.
The major downside is that you have to be willing to put an upper-bound on what "lots" of labels means. If lots can mean 100s, this won't work. If lots will definitely be less than the number of times you're willing to type Ctrl+V, you might be able to get this to work. You also have to be willing to put all the labels into a single ObservableCollection property in your view model. It sounded to me in your question that you already tried that out anyway though.
I takes advantage of AlternationIndex to get the index of the label and assign it to a column. Think I learned that from here. If an item has < x labels the extra columns won't get in the way. If an item has > x labels, the labels will start stacking on top of each other.
<!-- Increase AlternationCount and RowDefinitions if this template breaks -->
<ItemsControl ItemsSource="{Binding Labels}" IsTabStop="False" AlternationCount="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Column"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(ItemsControl.AlternationIndex)}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A"/>
<ColumnDefinition SharedSizeGroup="B"/>
<ColumnDefinition SharedSizeGroup="C"/>
<ColumnDefinition SharedSizeGroup="D"/>
<ColumnDefinition SharedSizeGroup="E"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Caliburn.Micro wiring a View to a ViewModel for display by a ContentControl

I'm having some trouble with loading a view into a ContentControl. I'm trying to keep this as simple as possible so I used the Hello project that comes with CM. I made sure that the Hello project compiles correctly, and runs. It displays a window with a textbox, and a button. Both the textbox and button are wired at runtime to the sample ViewModel.
I modified the ShellView.xaml and replaced the StackPanel control with the Grid control, and setup the grid with 4 rows and a single column. I assigned the textbox to the first row, the button to the second row, and then two separate ContentControl to the final two rows.
<Grid Width="800" Height="600">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" x:Name="Name" />
<Button Grid.Row="1" Grid.Column="0" x:Name="SayHello" Content="Click Me" />
<ContentControl Grid.Row="2" Grid.Column="0" x:Name="TopMenu"
VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"></ContentControl>
<ContentControl Grid.Row="3" Grid.Column="0" x:Name="BottomMenu"
VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"></ContentControl>
</Grid>
I created two separate C# classes in the ViewModels folder which are the ViewModels and are respectively called TopMenuViewModel.cs, and BottomMenuViewModel.cs. Both classes extend the PropertyChangedBase class. This is simply mimicking the ShellViewModel.cs class that comes with the sample project.
using System;
using Caliburn.Micro;
namespace TestWithCaliburnMicro.ViewModels
{
/// <summary>
/// Description of BottomMenuViewModel.
/// </summary>
public class BottomMenuViewModel : PropertyChangedBase
{
public BottomMenuViewModel()
{
}
}
I created two separate WPF User Controls in the Views folder which are the corresponding View and are respectively called TopMenuView.xaml and BottomMenuView.xaml. I added a Label in each xaml with the Content of "Top Menu" or "Bottom Menu" to differentiate between the two.
<Grid>
<Label>Bottom Menu View</Label>
</Grid>
In the ShellViewModel.cs class I created two public properties with only the "get" accessor set to return an instance of the corresponding ViewModel.
private BottomMenuViewModel _bottomMenu;
public BottomMenuViewModel BottomMenu {
get { return _bottomMenu; }
}
private TopMenuViewModel _topMenu;
public TopMenuViewModel TopMenu {
get { return _topMenu;}
}
Adding a break to the get accessor of either property shows that the get accessor is called when debugging the project. I added a simple statement to the constructor of the BottomMenuViewModel.cs class, such as int x = 0 and added a break to that line, but the break is never hit which to me means that the constructor is not called, so really the class is not created?
I believe what I'm doing is exceptionally basic and have read the All About Conventions document on the CM Codeplex site, and confirmed the logic with this comment: Prior question on stackoverflow
Hopefully someone will have the time to read this and point me in the right direction. Thanks.
Solution on GitHub. Note: made with SharpDevelop 4.x
GitHub solution
Either instantiate your view models in the constructor of the ShellViewModel, or if you wish to instantiate them at a later point, then add setters to your view model properties, and call the NotifyOfPropertyChange method to notify your UI that those property references have changed.

Is it good idea to mix ControlTemplate with user control?

Is it possible and a good idea to have user control (public MyControl: UserControl) which supports both ControlTemplates and existing content? I have understood that ControlTemplates should only be used when you inherit from Control (public MyControl: Control), but I found out that you can use them with UserControl too if your UserControl.xaml is empty.
Imagine I have control which has two rectangles side by side like the following:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid ShowGridLines="true" Height="100" Width="100">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Name="left" Grid.Column="0" Height="90" Fill="LightBlue"/>
<Rectangle Name="right" Grid.Column="1" Height="100" Fill="LightGreen"/>
</Grid>
</Page>
I would like the user of the control be able to replace those rectangles with whatever FrameworkElements he wants to use. So I need a ControlTemplate.
But in 99% of the cases user of the control is happy with the existing functionality so I would like him to be able to say:
Code behind:
mycontrol.Left.Fill = ....
XAML:
<mycontrol>
<mycontrol.Left.Fill = "Red"/>
</mycontrol>
That doesn't seem to be possible since if I support control templates I really don't have any UI elements or xaml. I only have the code behind file. I guess I could have a DependencyProperty Left but as long as I don't have some kind of container which would hold the content that would't do much good. I would have to create the grid in code behind file. Doesn't seem like a good idea.
And finally I would like to be able to use generics so the user can specify the type of the parts:
MyControl mycontrol<TLeft, TRight> = new MyControl<Rectangle, Button>();
This would help in code behind because of the type safety (no need to cast FrameworkElement into correct type). Unfortunately I don't think generics are really supported on the XAML side.
Is there any solution to this problem or is it really "Inherit from Control in order to support ControlTemplates but lose the easy usability of the control. Inherit from UserControl in order to support easy usability but lose the ControlTemplate support"?
Add a dependency property to the control:
public static DependencyProperty LeftFillProperty = DependencyProperty.
Register("LeftFill", typeof(Brush), typeof(MyControl));
public Brush LeftFill
{
get { return (Brush)GetValue(LeftFillProperty); }
set { SetValue(LeftFillProperty,value); }
}
Then in the default control template use:
<Rectangle Name="left" Grid.Column="0" Height="90" Fill="{TemplateBinding LeftFill}"/>
This will left you use (C#)
ctrl.LeftFill = Brushes.Red;
or (XAML)
<c:MyControl LeftFill="Red"/>
when you use the default template and when someone writes a new control template it's their responsibility to decide what to do with the LeftFill property (or to ignore it completely).
BTW, you should consider changing the names from "left" and "right" to something else ("MasterPanel" and "DetailPanel", "FoldersArea" and "FilesArea", whatever the logical usage in your control is), this will solve the issue of someone replacing the default template with a template that displays the same data in a different layout (top and bottom instead of left and right for example).

Resources