(First: I have some programming experience but am a beginner with XAML/WPF/StackOverflow. So please forgive anything "stupid" and ask if anything is not clearly described or you need additional info. Thanks.)
Introduction:
I have a base class Item with some properties (like Title, Notes, etc.).
From that I have some derived classes like ContactItem, MediaItem, etc. with additional properties which act as base classes for further specialized item types (e.g. ImageItem, MusicItem and VideoItem which are derived from MediaItem; Person and Institution are derived from ContactItem).
In WPF I want a page where multiple item types can be displayed together. I currently use an ItemsPanel for this and started to specify data templates for each item type - and there are many (more than 50).
Problem:
What I now want is some kind of "inheritance" of the item controls (e.g. have a "base" UserControl with a ContentPresenter and add additional controls for additional properties of derived classes/control templates).
What would be the best way to handle this in WPF/XAML without having to copy/paste the controls from the base classes for the derived item types in the data templates?
Any idea or hint in the right direction would be great.
If you need some code or additional info, please let me know.
You could simply nest each base class UserControl inside more derived UserControls, but you could find that this might soon become unmanageable. Another way would be to use the ContentControl element to display sections of larger DataTemplates from other DataTemplates like this:
<DataTemplate x:Key="NameTemplate" DataType="{x:Type ViewModels:UserViewModel}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UserDerivedViewModel}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource NameTemplate}" />
<TextBlock Text="{Binding Age}" />
</DataTemplate>
Related
i've two content controls how can i share a common property between them,
for example if i select some value from combobox in the first content control,
how can the second control know it
<telerikNavigation:RadTabItem Header="1">
<StackPanel>
<ContentControl Content="{Binding EGRPExtractViewModel.View}" />
</StackPanel>
</telerikNavigation:RadTabItem>
<telerikNavigation:RadTabItem Header="2">
<ContentControl Content="{Binding EGRPRightObjectViewModel.View}" />
</telerikNavigation:RadTabItem>
Thanks
You need to use two way binding then respond to the property changing in your ViewModel.
<ContentControl Content="{Binding EGRPRightObjectViewModel.View,Mode=TwoWay}" />
See the MSDN docs for how to respond to changed properties: http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx
You do not bind view properties. You can bind control properties in same view, so one possibility for you will be to create a control which exposes bindable properties specifically for this reason.
When using mvvm normally view-model should provide all needed properties to the view. If it's a property from another view-model, then it is still have to be provided by view-model of this view (search for questions about how to pass data between view-models to example, here is one).
I have a ViewModel that has a List of objects that have the same interface and to show them in the view we have a list of UserControls in our view model that the view binds to. I was wondering if I could create a template for the different types of concrete objects in the list and have WPF apply the correct one for me instead of creating the UserControl list and binding to that.
Basically I have:
<StackPanel
Margin="0,0,20,0"
>
<my2:ProfileIdentificationView />
<ItemsControl
ItemsSource="{Binding Path=ProfileSections}">
<ItemsControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DockPanel>
Where the ItemsSource binding is the list of user controls. I am looking for a way to apply the look of those UserControls here based on the type of the ProfileSection. Like one type is an IIS section and another Type is a Users section.
You can create implciit DataTemplates for your view models via the DataTemplate.DataType. Make sure to use the x:Type markup extensions as strings are interpreted as XML-element tags (to allow implicit templating of XML).
I'm new to WPF and using MVVM. I have a view in which I want to display different content according to what a user selects on a menu. One of those things is another user control Temp which has a view model (TempVM) so I am doing this:
<ContentControl Content="{Binding Path=TempVM}"/>
and TempVM (of type TempViewModel)is null until the user clicks a button. Its data template is this
<DataTemplate DataType="{x:Type vm:TempViewModel}">
<view:Temp />
</DataTemplate>
That's fine, but the other thing I want to do is show a listbox when a user clicks a different menu item. So I am trying to do
<ContentControl Content="{Binding Path=Missions}"/>
(Missions is an observable collection of MissionData) and trying to template it like this:
<DataTemplate DataType="{x:Type ObservableCollection(MissionData)}">
<StackPanel>
<ListBox ItemsSource="{Binding}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go"/>
</StackPanel>
</DataTemplate>
But the compiler doesn't like the type reference. If I try doing it by giving the template a key and specifying that key in the ContentControl it works but obviously I see the ListBox and button when there's no Missions. Obviously I could make a user control and viewmodel and follow the same pattern as I did for the TempVM but it seems over the top. Am I going the right way about this and what do I need to do?
From what i see is that you try to use a Collection as a dataobject which is in my opinion bad practice. Having a DataTemplate for a collection is also problematic, like you already have witnessed. I would advice you to use a ViewModel for your missions collection.
class MissionsSelectionViewModel
{
public ObservableCollection<Mission> Misssions;
public MissionData SelectedMission;
public ICommand MissionSelected;
}
and modify your datatemplate to
<DataTemplate DataType="{x:Type MissionsSelectionViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Missions}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go" Command="{Binding MissionSelected}/>
</StackPanel>
</DataTemplate>
If I were to follow your pattern of implicit templates, I would derive a custom non-generic collection MissionDataCollection from ObservableCollection<MissionData> and use it to keep MissionData items. Then I would simply reference that collection in DataType. This solution gives other advantages like events aggregation over the collection that are useful.
However, it seems to me that the best solution is the following.
Add a IsMissionsListVisible property to your VM.
Bind the Visibility property of the ContentControl showing the list to the IsMissionsListVisible property.
Use a keyed DataTemplate resource.
Implement the logic that determines if IsMissionsListVisible. Supposedly it should be true when there is at least one mission in the selected item. But the logic may be more complex.
I would do it this way. In fact, I do it this way usually, and it gives several benefits. The most important is that I can explicitly control the logic of content visibility in various situations (e.g. async content refresh).
I have about a dozen different views, which are pretty much identical except for the names of the properties they bind to. For example, the below sections are form two different views:
<TextBlock Text="{Binding PersonName}">
<GroupBox Header="{Binding PersonName}">
<ComboBox Text="{Binding SelectedPersonName}" SelectedItem="{Binding SelectedPerson}" ItemsSource="{Binding People}" DisplayMemberPath="PersonName"/>
</GroupBox>
<igDP:XamDataGrid DataSource="{Binding PersonEntries}"
<TextBlock Text="{Binding CarName}">
<GroupBox Header="{Binding CarName}">
<ComboBox Text="{Binding SelectedCarName}" SelectedItem="{Binding SelectedCar}" ItemsSource="{Binding Cars}" DisplayMemberPath="CarName"/>
</GroupBox>
<igDP:XamDataGrid DataSource="{Binding CarEntries}"
Note that the only real differences between these to blocks are the names of the bindings used (Person vs Car).
I was thinking of maybe creating one BaseView class that the other views inherit from. This base class would use generic enough binding names so that it can be reused, such as:
<TextBlock Text="{Binding DataItemName}">
<GroupBox Header="{Binding DataItemName}">
<ComboBox Text="{Binding SelectedDataItemName}" SelectedItem="{Binding SelectedDataItem}" ItemsSource="{Binding DataItems}" DisplayMemberPath="DataItemName"/>
</GroupBox>
<igDP:XamDataGrid DataSource="{Binding DataItemEntries}"
This way, my PersonsView and CarsView can inherit from BaseView and that's it. I would also have to make changes to the ViewModels though, so that they expose the correctly named properties, such as DataItem. I guess I could create a base ViewModel interface that exposes the desired properties and have the other ViewModels implement that.
Any thoughts on the above? Would it be a bad idea to try to create a base view or base view model as I described?
Thanks.
You're really going to create the inheritance in your view models, not your views. I'd define an ItemViewModelBase class that exposes ItemName, Items, and SelectedItemName properties and derive my view models from it.
The views themselves don't really "inherit" per se. In fact, unless you need customization in the view, you don't need multiple views: you only need one view that presents ItemViewModelBase objects.
Of course, if you do need the views to be different, you can do a certain amount of customization, e.g.:
<DataTemplate DataType="{x:Type CarsViewModel}">
<DockPanel>
<Label DockPanel.Dock="Top">Cars</Label>
<local:ItemView/>
</DockPanel>
</DataTemplate>
This is a cool idea for another reason. Right now, if you don't provide a data template, whenever WPF presents an object it creates a TextBlock containing object.ToString(). Implementing a generic base class gives you a way to globally override this behavior just by creating one data template, e.g.:
<DataTemplate DataType="{x:Type ItemViewModelBase}">
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
That's not easier than just overriding ToString() to return ItemName (which is where I'd start), but if (for instance) you want a ToolTip that displays detailed information when the user mouses over it, you just add it to this one template and it works everywhere in your UI.
May be you can continue having one generic view model, but having, instead, multiple
data layers. This can basically help you to push complexity on data layer,which
is basically easier to test and debug. But everything is too context dependent.
Good luck.
Say I have an interface like this:
public interface ISomeInterface
{
...
}
I also have a couple of classes implementing this interface;
public class SomeClass : ISomeInterface
{
...
}
Now I have a WPF ListBox listing items of ISomeInterface, using a custom DataTemplate.
The databinding engine will apparently not (that I have been able to figure out) allow me to bind to interface properties - it sees that the object is a SomeClass object, and data only shows up if SomeClass should happen to have the bound property available as a non-interface property.
How can I tell the DataTemplate to act as if every object is an ISomeInterface, and not a SomeClass etc.?
Thanks!
In order to bind to explicit implemented interface members, all you need to do is to use the parentheses. For example:
implicit:
{Binding Path=MyValue}
explicit:
{Binding Path=(mynamespacealias:IMyInterface.MyValue)}
This answer from Microsoft forums by Beatriz Costa - MSFT is worth reading (rather old):
The data binding team discussed adding support for interfaces a while ago but ended up not implementing it because we could not come up with a good design for it. The problem was that interfaces don't have a hierarchy like object types do. Consider the scenario where your data source implements both IMyInterface1 and IMyInterface2 and you have DataTemplates for both of those interfaces in the resources: which DataTemplate do you think we should pick up?
When doing implicit data templating for object types, we first try to find a DataTemplate for the exact type, then for its parent, grandparent and so on. There is very well defined order of types for us to apply. When we talked about adding support for interfaces, we considered using reflection to find out all interfaces and adding them to the end of the list of types. The problem we encountered was defining the order of the interfaces when the type implements multiple interfaces.
The other thing we had to keep in mind is that reflection is not that cheap, and this would decrease our perf a little for this scenario.
So what's the solution? You can't do this all in XAML, but you can do it easily with a little bit of code. The ItemTemplateSelector property of ItemsControl can be used to pick which DataTemplate you want to use for each item. In the SelectTemplate method for your template selector, you receive as a parameter the item you will template. Here, you can check for what interface it implements and return the DataTemplate that matches it.
The short answer is DataTemplate's do not support interfaces (think about multiple inheritance, explicit v. implicit, etc). The way we tend to get around this is to have a base class things extend to allow the DataTemplate specialization/generalization. This means a decent, but not necessarily optimal, solution would be:
public abstract class SomeClassBase
{
}
public class SomeClass : SomeClassBase
{
}
<DataTemplate DataType="{x:Type local:SomeClassBase}">
<!-- ... -->
</DataTemplate>
You have another option. Set a Key on your DataTemplate and reference that key in the ItemTemplate. Like this:
<DataTemplate DataType="{x:Type documents:ISpecificOutcome}"
x:Key="SpecificOutcomesTemplate">
<Label Content="{Binding Name}"
ToolTip="{Binding Description}" />
</DataTemplate>
then reference the template by key where you want to use it, like this:
<ListBox ItemsSource="{Binding Path=SpecificOutcomes}"
ItemTemplate="{StaticResource SpecificOutcomesTemplate}"
>
</ListBox>
The answer suggested by dummyboy is the best answer (it should be voted to the top imo). It does have an issue that the designer doesn't like it (gives an error "Object null cannot be used as an accessor parameter for a PropertyPath) but there is a good workaround. The workaround is to define the item in a datatemplate and then set the template to a label or other content control. As an example, I was trying to add an Image like this
<Image Width="120" Height="120" HorizontalAlignment="Center" Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Name="mainImage"></Image>
But it kept giving me the same error. The solution was to create a label and use a data template to show my content
<Label Content="{Binding}" HorizontalAlignment="Center" MouseDoubleClick="Label_MouseDoubleClick">
<Label.ContentTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=(starbug:IPhotoItem.PhotoSmall)}" Width="120" Height="120" Stretch="Uniform" ></Image>
</StackPanel>
</DataTemplate>
</Label.ContentTemplate>
</Label>
This has its downsides but it seems to work pretty well for me.
Note: You can use more complex multi-part paths like this too if the interface property is inside a path:
<TextBlock>
<TextBlock.Text>
<Binding Path="Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode"/>
</TextBlock.Text>
</TextBlock>
Or directly with the Binding directive.
<TextBlock Text="{Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>
Or when using multiple properties of an interface you can redefine the DataContext locally to make the code more readable.
<StackPanel DataContext={Binding Path=Packages[0].(myNamespace:IShippingPackage.ShippingMethod)}">
<TextBlock Text="{Binding CarrierName}"/>
<TextBlock Text="{Binding CarrierServiceCode}"/>
</StackPanel>
Tip: Watch out for accidentally ending up with )}at the end of a Path expression. Stupid copy/paste error I keep making.
Path="(myNameSpace:IShippingPackage.ShippingMethod)}"
Make sure to use Path=
Discovered that if I don't explicitly use Path= then it may not be able to parse the binding.
Typically I will just write something like this:
Text="{Binding FirstName}"
instead of
Text="{Binding Path=FirstName}"
But with the more complex interface binding I found that Path= was needed to avoid this exception:
System.ArgumentNullException: Key cannot be null.
Parameter name: key
at System.Collections.Specialized.ListDictionary.get_Item(Object key)
at System.Collections.Specialized.HybridDictionary.get_Item(Object key)
at System.ComponentModel.PropertyChangedEventManager.RemoveListener(INotifyPropertyChanged source, String propertyName, IWeakEventListener listener, EventHandler`1 handler)
at System.ComponentModel.PropertyChangedEventManager.RemoveHandler(INotifyPropertyChanged source, EventHandler`1 handler, String propertyName)
at MS.Internal.Data.PropertyPathWorker.ReplaceItem(Int32 k, Object newO, Object parent)
at MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)
i.e. don't do this:
<TextBlock Text="{Binding Packages[0].(myNamespace:IShippingPackage.ShippingMethod).CarrierServiceCode}"/>