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.
Related
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.
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.
I'm making an application for DB migrations. I made a multithreaded framework with WPF GUI. I put someting like this in my namespace/folder:
class Something : Migrator {
public override Run(){
//I would need this
string valueOfMyCustomFieldOnForm = xyz.Text; //example
int count = 500;
for(int i = 0; i < 500; i++){
//do something here
OnProgressChanged(...); //call event, GUI is updated
}
OnCompleted(...); //migration completed
}
}
Then using reflection I put all classes in that namespace onto dropdown list. When I choose one in a list and click Start, the Thread with code in Run method is started.
DB Host: TEXTBOX
DB Username: TEXTBOX
DB Password: TEXTBOX
--
Migrator custom field 1: TEXTBOX
Migrator custom field 2: TEXTBOX
...
--
List with migrated items - irrelevant
There are few commong field on GUI (like database host, username etc...). But for some of those migrators I would need custom fields on GUI (for example 3 extra textbox fields).
What is the best way to do this in WPF? I need part of the GUI to be dynamic.
There's a lot of seemingly-irrelevant information in your question, which - I think - is really about mechanisms for creating metadata-driven UIs in WPF. Here's a way to approach that problem:
Suppose that you want to build a property-sheet-like UI: a grid that displays a row for each property, with a prompt and an input control of some kind. To do this, you're going to need a collection of objects, with each item in the collection including properties that describe the property and its value. A simple design would be a class that exposes a Prompt property and a Value property and that implements change notification.
Once you have created and populated this collection, you can implement an ItemsControl that displays it in a grid:
<ItemsControl ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="PropertyViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Prompt"/>
<ColumnDefinition SharedSizeGroup="Value"/>
</Grid.ColumnDefinition>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
</Grid>
<Label Content="{Binding Prompt}"/>
<TextBox Grid.Column="1" Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is pretty simple - the most complicated thing about it is using Grid.IsSharedSizeScope so that all of the grids that this control creates use the same column widths. You could also use a ListView instead of an ItemsControl, though using a ListView for this introduces a bunch of issues surrounding focus and selection that you may not want to deal with.
Note that because of the magic that is WPF template matching, you could conceivably implement the Value property as an object, and create different templates to handle the different possible types of the Value property - just like a real property sheet does. To do this, you'd create a template for each type, e.g.:
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:DateTime}">
<DatePicker Value="{Binding Value, Mode=TwoWay}"/>
</DataTemplate>
etc. Then you'd change the template for the PropertyViewModel so that instead of showing the Value in a TextBox, it uses a ContentPresenter, e.g.:
<ContentPresenter Grid.Column="1" Content="{Binding}"/>
I'm new to WPF and before I dive in solving a problem in completely the wrong way I was wondering if WPF is clever enough to handle something for me.
Imagine I have a collection containing objects. Each object is of the same known type and has two parameters. Name (a string) and Picked (a boolean).
The collection will be populated at run time.
I would like to build up a UI element at run time that will represent this collection as a series of checkboxes. I want the Picked parameter of any given object in the collection updated if the user changes the selected state of the checkbox.
To me, the answer is simple. I iterate accross the collection and create a new checkbox for each object, dynamically wiring up a ValueChanged event to capture when Picked should be changed.
It has occured to me, however, that I may be able to harness some unknown feature of WPF to do this better (or "properly"). For example, could data binding be employed here?
I would be very interested in anyone's thoughts.
Thanks,
E
FootNote: The structure of the collection can be changed completely to better fit any chosen solution but ultimately I will always start from, and end with, some list of string and boolean pairs.
I would strongly recommend the ItemsControl, its behaviour is as close as you can get to the ASP.Net repeater control so it is very flexible.
Declare the item control as:
<ItemsControl Name="YourItemsControl"
ItemsSource="{Binding Path=YourCollection}"
ItemTemplate="{StaticResource YourTemplate}">
</ItemsControl>
Then you can use the datatemplate to organise the data into a display format for the user
<DataTemplate x:Key="ProjectsTemplate">
<StackPanel Margin="0,0,0,10">
<Border CornerRadius="2,2,0,0" Background="{StaticResource ItemGradient}" d:LayoutOverrides="Width, Height">
<local:ItemContentsUserControl Height="30"/>
</Border>
...
Useful ItemsControl Links
http://drwpf.com/blog/itemscontrol-a-to-z/
http://www.galasoft.ch/mydotnet/articles/article-2007041201.aspx
I hope this helps you.
You can use Data Templates. Here's a good post about it.
This is exactly the kind of scenario WPF simplifies. Event-handlers- bah! Data-binding and data templates make this a cinch. I have constructed an example illustrating how you can do this.
Here is the code-behind, which declares a class to represent your items- PickedItem. I then create a collection of these items and populate it with some samples.
public partial class DataBoundCollection : Window
{
public DataBoundCollection()
{
Items = new ObservableCollection<PickedItem>();
Items.Add(new PickedItem("Item 1"));
Items.Add(new PickedItem("Item 2"));
Items.Add(new PickedItem("Item 3"));
InitializeComponent();
}
public ObservableCollection<PickedItem> Items
{
get;
set;
}
}
public class PickedItem
{
public PickedItem(string name)
{
Name = name;
Picked = false;
}
public string Name
{
get;
set;
}
public bool Picked
{
get;
set;
}
}
Now, let's look at the XAML mark-up for this window:
<Window x:Class="TestWpfApplication.DataBoundCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBoundCollection" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Picked}" Margin="5"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I create a ListBox to hold the items, and bind its ItemsSource property to the collection I created in the code-behind. Then, I provide the ListBox with an ItemTemplate, which determines how each PickedItem will be rendered. The DataTemplate in this case is as simple as a check-box and some text, both bound to the member variables on PickedItem. Now, when I check any of these items, the data in the underlying collection is modified, in real-time, with no event handlers needed. Ta-da!
alt text http://img405.imageshack.us/img405/1083/databoundcollection.png
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).