Typed Data Templates in Silverlight - silverlight

My understanding is that Silverlight does not support DataTemplates with a DataType attribute.
How then would you accomplish the following in SL (author is Josh Smith, full link below). In a nutshell, he's saying that if you bind a TabControl's tab pages to a collection of ViewModels, WPF will figure out how to display each one on the fly by looking for a DataTemplate that has the appropriate (matching) DataType set. Way cool, but I'm wondering how you would (could?) do this in Silverlight.
Applying a View to a ViewModel
MainWindowViewModel indirectly adds
and removes Workspace­ViewModel
objects to and from the main window's
Tab­Control. By relying on data
binding, the Content property of a
TabItem receives a
ViewModelBase-derived object to
display. ViewModelBase is not a UI
element, so it has no inherent support
for rendering itself. By default, in
WPF a non-visual object is rendered by
displaying the results of a call to
its ToString method in a TextBlock.
That clearly is not what you need,
unless your users have a burning
desire to see the type name of our
ViewModel classes!
You can easily tell WPF how to render
a ViewModel object by using typed
DataTemplates. A typed DataTemplate
does not have an x:Key value assigned
to it, but it does have its DataType
property set to an instance of the
Type class. If WPF tries to render one
of your ViewModel objects, it will
check to see if the resource system
has a typed DataTemplate in scope
whose DataType is the same as (or a
base class of) the type of your
ViewModel object. If it finds one, it
uses that template to render the
ViewModel object referenced by the tab
item's Content property.
The MainWindowResources.xaml file has
a Resource­Dictionary. That dictionary
is added to the main window's resource
hierarchy, which means that the
resources it contains are in the
window's resource scope. When a tab
item's content is set to a ViewModel
object, a typed DataTemplate from this
dictionary supplies a view (that is, a
user control) to render it, as shown
in Figure 10.in Figure 10.
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in Figure 10.

Here is ONE way you can do it. I have used a technique like this in the past, and had great success with it.
Consider a very simple container that will create the view for you like this:
public class ViewMapper : ContentControl
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == "DataContext")
WhenDataContextChanges();
}
private void WhenDataContextChanges()
{
if (DataContext == null)
Content = null;
else
Content = ViewFactory.GetView(DataContext.GetType());
}
}
EDIT
So, you can use this control to do the mapping for you:
<Border DataContext="{Binding MyViewModel}">
<ViewMapper />
</Border>
END EDIT
Note that ViewMapper simply waits for the data context to change, looks up the appropriate view for the data type, and creates a new one. It relies on ViewFactory, which is a very simple static lookup that maps types to views:
public class ViewFactory
{
private static readonly Dictionary<string, Func<UIElement>> _registry = new Dictionary<string, Func<UIElement>>();
private static string Key(Type viewModelType)
{
return viewModelType.FullName;
}
public static void RegisterView(Type viewModelType, Func<UIElement> createView)
{
_registry.Add(Key(viewModelType), createView);
}
public static UIElement GetView(Type viewModelType)
{
var key = Key(viewModelType);
if (!_registry.ContainsKey(key))
return null;
return _registry[key]();
}
}
Then, you simply need to register the view mappings some place:
ViewFactory.RegisterView(typeof(SomeViewModel), () => new SomeView());
Note that ViewFactory could just as easily use Activator.CreateInstance instead of using the Func mechanism. Take that one step further, and you can use an IoC container... You could always decide to map via a string Name property on the ViewModel instead of a type... the possibilities are endless and powerful here.

Related

How can I tell when the ItemsSource of my Custom control has changed

I have a custom control in the early stages of development as I endeavour to learn about wpf custom control development. The custom control inherits from ItemsControls which gives me access to an ItemsSource property to which I am binding an enumerable collection.
Currently I have a simple two project solution comprising my custom control in one and a test project in the other to test the former. In my test project I have a simple mainwindow onto which I have put my custom control and bound its ItemsSource.
<WpfControls:VtlDataNavigator x:Name="MyDataNavigator"
ItemsSource="{Binding ElementName=MainWindow, Path=Orders}" />
In the loaded event of the main window (which implements INotifyPropertyChanged) I instantiate the Orders collection. The customcontrols gets initialised before the main window loads but I can see from examining the Live Visual Tree in visual studio that once the main form loads the custom controls Items Source property is indeed set to Orders. Now of course I'd actually like to count the orders and have my custom control display that (it's a simple data navigator so what I'm after is the record count). I know how to get the count but how do I know when the itemsSource has changed so that I can react to it and get the count. There's no itemsSourceChanged event that I can see.
I've seen this blog article, but I'm wondering if there is a more straightforward approach to this as it seems such an obvious thing to want to know about.
You can do that using OverrideMetaData.
Try this:
public class Class1 : ItemsControl
{
static Class1()
{
ItemsSourceProperty.OverrideMetadata(typeof(Class1),
new FrameworkPropertyMetadata(null, OnItemSourceChanged));
}
private static void OnItemSourceChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Why you haz Changed me!");
}
}
The ItemsSource is a DependencyProperty, and when creating DPs you can optionally specify a "property changed" event. Unfortunately ItemsSource is locked away in the base class, so I started wondering if there might be a way to add your own event to an existing DP. I came across this article that looks promising. Basically you would do something like this (untested so read the article!):-
var dpd = DependencyPropertyDescriptor.FromProperty(
VtlDataNavigator.ItemsSourceProperty,
typeof(VtlDataNavigator));
if (dpd != null)
{
dpd.AddValueChanged(
vtlDataNavigatorInstance,
delegate
{
var count = VtlDataNavigatorInstance.ItemsSource.Count; // Or whatever...
});
}

Can custom UIElement implement IList<UIElement> and be assigned children directly (in XAML)?

Scenario: I have a range of custom UIElements (in fact, I have replaced all the standard WPF FrameworkElements I would use with lighter, more efficient counterparts) for a custom layout system intended to only use those. They all inherit from a class called Surface (which in turn is a direct descendant of UIElement).
I am now wondering if my version of Panel (let's call it SurfacePanel) can simply implement IList<Surface> and allow child Surface elements to be added directly to it, rather than to a Children property (as with regular WPF panels), in XAML.
To illustrate - in codebehind, I can do now this:
SurfacePanel.Add(child);
And from that, I would like to be able to do this in XAML:
<SurfacePanel>
<child />
</SurfacePanel>
But XAML seems to require me to have a codebehind pattern like this:
SurfacePanel.Children.Add(child)
(I don't really need these controls to support XAML to work in the runtime environment, but when testing and prototyping, I like to make my UI controls "XAML friendly" so I can benefit from the visual designer in VS (along with the property pane etc), if nothing more than as a 'preview' window).
Since my controls inherit from UIElement (and have the proper Measure/Arrange/Render overrides and so on), they function quite well when put on, say, a regular Canvasor Grid. But the VS XAML parser is not too happy about my SurfacePanel (that implements IList<Surface>) when I am adding children to it in markup. It says "Cannot add content to an object of type "SurfacePanel"".
I know that if I add a Children property of an appropriate type and add an attribute to the SurfaceCanvas class ([ContentProperty("Children")]), it will work. But since the SurfacePanel is itself a collection capable of the same thing, is there a way to make XAML 'get it'?
Edit:
I can solve the XAML 'compliance' by adding a Children property on the SurfacePanel that simply returns its inner List, but then adding and removal of elements on that directly bypasses the internal logic that wire the child elements up.
If the inner list was an ObservableCollection, I could do it the conventional way and do the wiring in a CollectionChanged event handler - but basically the whole point of integrating IList in the Panel directly is to avoid that..
Edit 2:
This "works" (but bypasses the wiring):
[ContentProperty("Children")]
public class SurfacePanel : Surface, IList<Surface>
{
private readonly List<Surface> _children = new List<Surface>();
public List<Surface> Children
{
get { return _children; }
}
}
I cannot return this because SurfacePanel is not a List<Surface>, but an IList<Surface>.
If I change the property to
public IList<Surface> Children
{
get { return this; }
}
I get an error message even with the following XAML (but not with <m:SurfacePanel/>):
<m:SurfacePanel>
</m:SurfacePanel>
The error message is
Cannot set content property 'Children' on element 'SurfacePanel'. 'Children' has incorrect access level or its assembly does not allow access.
Also implement IList and declare the Children property like this:
[ContentProperty("Children")]
public class SurfacePanel : Surface, IList, IList<Surface>
{
public IList Children
{
get { return this; }
}
...
}

WPF: DependencyProperty of custom control fails when using several instances of the control

I've built a custom control in WPF that inherits from ListBox. In this I have implementet my own property that is a BindingList. To make this property bindable I've implemeneted it as a DependencyProperty:
public BindingList<CheckableListItem> CheckedItems
{
get
{
return (BindingList<CheckableListItem>)GetValue(MultiComboBox.CheckedItemsProperty);
}
set
{
SetValue(MultiComboBox.CheckedItemsProperty, value);
}
}
public static readonly DependencyProperty CheckedItemsProperty;
I register this DependencyProperty in a static constructor inside my custom control:
CheckedItemsProperty = DependencyProperty.Register("CheckedItems",
typeof(BindingList<CheckableListItem>),
typeof(MultiComboBox),
new FrameworkPropertyMetadata(new BindingList<CheckableListItem>()));
(MultiComboBox is the name of my custom control. CheckableListItem is a simple class I've written just for this purpose).
This BindingList is then updated inside the custom control (never outside) as the user interacts with it.
When I use my custom control in XAML I bind to the CheckItems property with the mode "OneWayToSource". I'm using the MVVM pattern and the property in the ViewModel that I'm binding to is also a BindingList. The ViewModel never affects this list, it just reacts at the changes that the custom control make to the list. The property in the ViewModel looks like this:
private BindingList<CheckableListItem> _selectedItems;
public BindingList<CheckableListItem> SelectedItems
{
get
{
return _selectedItems;
}
set
{
if (value != _selectedItems)
{
if (_selectedItems != null)
{
_selectedItems.ListChanged -= SelectedItemsChanged;
}
_selectedItems = value;
if (_selectedItems != null)
{
_selectedItems.ListChanged += SelectedItemsChanged;
}
OnPropertyChanged("SelectedItems");
}
}
}
As you can see I'm listening to changes made to the list (these changes always occur inside my custom control), and in the "SelectedItemsChanged"-method I update my Model accordingly.
Now...this works great when I have one of these controls in my View. However, if I put two (or more) of them in the same View strange things start to happen. This will of course mean that I'll have two lists with selected items in my ViewModel. But if do something in the View that changes one of the lists, both lists are affected! That is, the event handlers for the event ListChanged is triggered for both list if changes are made to any one of them!
Does anyone recognize this problem and/or have a solution to it? What is wrong with my implementation?
My first though is that the DependencyProperty is static. Normally that means shared between all instances. But I guess DependencyProperties work in some other "magical" way so that might not be the problem.
Any tips or hints is appreciated!
I had a similar problem with a collection-type dependency property. My solution was taken from the MSDN article on Collection-Type Dependency Properties. It was adding the following line
SetValue(OperatorsPropertyKey, new List<ListBoxItem>()); //replace key and type
in the constructor of my control because it seems that a collection-type dependency property constructor is being called only once no matter how many instances your control containing this collection has (static eh).
This sounds like you bound both/all the Views to the same ViewModel. That would explain that changes to one cause changes in the other.

How do you call identically named properties on different types which don't share an interface?

I have a DataTemplate that needs to set the IsSelected property on an ItemsControl's container (such as TreeViewItem, ListViewItem or ComboBoxItem). However, it doesn't know the type of the container until it's passed in to it. Since IsSelected isn't part of a common base class or interface, nor is it a common dependency property registered with AddOwner to the various classes (Duh, MS!!! WTF not?!!), I ended up with this mess...
if (container is TreeViewItem) {
(container as TreeViewItem).IsSelected = true;
return;
}
if (container is ListBoxItem) {
(container as ListBoxItem).IsSelected = true;
return;
}
if (container is ComboBoxItem) {
(container as ComboBoxItem).IsSelected = true;
return;
}
...which not only is verbose, but requires me to modify it if I ever use a different ItemsControl that uses different container types! Not good!
Sure I could enhance it a little by putting this logic in extension methods (damn C# for not having extension properties!!) called IsContainerSelected and SetContainerSelected and putting them on UIElement, then moving the above code inside there, but it's just making the outside neater. The inside is still a mess.
My only other thought is to use reflection and look for an IsSelected property and use that if found, but I'm always leery of doing things like that. However, since there isn't a common interface or base class, I'm not really sure I have a choice here.
For context, I'm sharing a complex data template between several different ItemsControls and the template itself has controls that can receive focus such as checkbox and textbox. However, when those controls receive focus via the mouse, the underlying container item doesn't get selected and whatever was selected before remains so.
My workaround is to use an attached behavior that utilizes the preview events to intercept the focus before it happens and set the underlying item accordingly, which works great when I've hard-coded TreeViewItem or ListBoxItem, etc., but I don't want to hard-code the type since the control shouldn't really care. So that's the part that breaks down.
Ugh!!! Why didn't MS just register the same attached property or at least create an ISelectableContainer interface?!!
I have read your answer, and it does make sense - in your case, IsSelected may obviously be part of the ViewModel, and that seems to be the best solution in your case.
But you asked for further explanation about C# dynamic features. C# 4.0 now has some dynamic functionalities, which allow us to create code that would only be possible in languages like Python, Ruby or JavaScript. This, of course, has its cost - a dynamic abuse would not only make code slower, but also more confusing - because you would lose compile-time errors and IntelliSense.
I have written a simple example so you may understand it better:
public class ClassOne
{
public int SameProperty { get; set; }
}
public class ClassTwo
{
public int SameProperty { get; set; }
}
public class ClassThree
{
public string SameProperty { get; set; }
}
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
dynamic wrapper = new ClassOne();
wrapper.SameProperty = 5;
wrapper = new ClassTwo();
wrapper.SameProperty = 15;
wrapper = new ClassThree();
wrapper.SameProperty = "Now it is a string!";
// And now a run-time error...
wrapper.AnotherProperty = "And this won't work...";
}
}
As you can see, wrapper has no definite type whatsoever - a dynamic reference will allow any kind of method or property invocation, since the actual binding will only be made during run-time, not compile-time.
Of course, this example is very naive, but sometimes dynamic code may be useful - it is a good option to avoid explicit reflection, or to avoid long if...else statements based on type (like your snippet above).
I'm not sure that I fully understand your problem, but you could try adding an IsSelected boolean to your model and then binding that property against the Item control it's contained in. That way, you just have to worry about setting that property in the model, regardless of the container.
Per #mdm20's answer, he suggested modifying the ViewModel, which is of course normally what you want to do. However this is a purely view-related issue (keyboard navigation-related) and isn't reflected in the ViewModel at all, nor in this case should it be.
But that gave me an idea! Since I'm using a custom control to render the item in whichever items control (via its data template) it's being added to, that control naturally does have multiple instances (all of which are pointing to the same ViewModel instance), which is what I want!
Therefore, rather than adding the IsSelected to the ViewModel, I added it to the user control itself, then I just bind to that within the data template for the respective ItemsControl which I do know about. I can then set the IsSelected property in the code-behind for the user control as needed (i.e. during the preview mouse events, etc.) and the underlying ItemsControl responds appropriately! Works great and keeps the ViewModel clean since neither the model, nor the viewmodel need to know about it. The IsSelected remains purely in the UI which is where in this particular case it should be!

WPF element that dynamically creates (encapsulated) children at runtime

I want to create a WPF element that, at runtime, is in full control of its child elements -- adding and removing child UI when its properties change. Something a bit like what ItemsControl does when you modify its ItemsSource property, though in my case there'll only be one child.
This will be a view container for MVVM -- when you give it a Model or a ViewModel, it will magically create the correct View and wire everything up. There's no need for my view container to be templatable (since it creates user-defined views, which are UserControls and have their own templates), and I'd prefer that it encapsulate as much as possible. I could probably do this easily by descending from something like Grid, and adding child controls when my own properties change; but Grid publicly exposes its collection of child elements and lets anyone add and remove stuff.
Which WPF class should I descend from for maximum encapsulation, and how do I add child elements to it at runtime?
Based on my understanding of the docs, I tried using FrameworkElement and AddVisualChild, just to see if I could create child controls at runtime. I'm not clear on whether the AddLogicalChild is necessary, but I put it in just in case:
public class ViewContainer : FrameworkElement {
private TextBlock _child;
public ViewContainer() {
_child = new TextBlock { Text = "ViewContainer" };
AddLogicalChild(_child);
AddVisualChild(_child);
InvalidateMeasure();
}
public object Content { get; set; }
protected override Size ArrangeOverride(Size finalSize) {
_child.Arrange(new Rect(finalSize));
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
_child.Measure(availableSize);
return _child.DesiredSize;
}
}
When I put a ViewContainer into a Window, and run this, I expect to see a TextBlock saying "ViewContainer". But instead, I just see a blank window. So obviously I'm missing something.
How can I fix the above code so that the "child" control does appear at runtime, but isn't exposed for others to mess with (any more than can be avoided)?
To answer your specific question, you'll also need to override GetVisualChild and VisualChildrenCount properties to enable your child element to be displayed.
Have you thought about taking advantage of WPF's support for implicit DataTemplates?
The way I have handled a requirement similar to yours is by using a ContentControl. I bind the Content property to my ViewModel. I then make sure that in Resource Dictionaries referenced somewhere in the tree above the ContentControl I have DataTemplates defined for all the types of ViewModels that might be assigned to the Content Property.
This way WPF takes care of wiring up the correct view for my ViewModel.

Resources