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}"/>
Related
(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>
We have a WPF application with a standard MVVM pattern, leveraging Cinch (and therefore MefedMVVM) for View -> ViewModel resolution. This works well, and I can bind the relevant controls to properties on the ViewModel.
Within a particular View, we have an Infragistics XamGrid. This grid is bound to an ObservableCollection on the ViewModel, and displays the appropriate rows. However, I then have a specific column on this grid which I am trying to bind a TextBox text value to a property on the parent DataContext, rather than the ObservableCollection. This binding is failing.
We've gone through several options here including:
Using AncestorType to track up the tree and bind to the DataContext of the parent UserControl like so (from the great answer to this question, as well as this one)...
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Specifying the ElementName and trying to target the top level control directly. Have a look here if you'd like to read about using ElementName.
Using a 'proxy' FrameorkElement defined in the resources for the UserControl to try and 'pass in' the context as required. We define the element as below, then reference as a static resource...
<FrameworkElement x:Key="ProxyContext" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"></FrameworkElement>
In this case the binding finds the FrameworkElement, but can not access anything beyond that (when specifying a Path).
Having read around, it looks quite likely that this is caused by the Infragistics XamGrid building columns outside of the tree. However, even if this is the case, at least options 2 or 3 should work.
Our last thoughts are that it is related to the V - VM binding, but even using Snoop we've yet to find what the exact issue is. I'm by no means an expert with WPF binding so any pointers would be appreciated.
EDIT: I have found some templating examples from Infragistics here that I will try.
EDIT 2: As pointed out by #Dtex, templates are the way to go. Here is the relevant snippet for use with a XamGrid:
<ig:GroupColumn Key="CurrentDate">
<ig:GroupColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.CurrentDateTest, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</ig:GroupColumn.HeaderTemplate>
<ig:GroupColumn.Columns>
I've left the XML open... you'd simply add the columns you wanted, then close off the relevant tags.
I dont know about XamGrid but that's what i'll do with a standard wpf DataGrid:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Since the TextBlock and the TextBox specified in the cell templates will be part of the visual tree, you can walk up and find whatever control you need.
Because of things like this, as a general rule of thumb, I try to avoid as much XAML "trickery" as possible and keep the XAML as dumb and simple as possible and do the rest in the ViewModel (or attached properties or IValueConverters etc. if really necessary).
If possible I would give the ViewModel of the current DataContext a reference (i.e. property) to the relevant parent ViewModel
public class ThisViewModel : ViewModelBase
{
TypeOfAncestorViewModel Parent { get; set; }
}
and bind against that directly instead.
<TextBox Text="{Binding Parent}" />
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.
Firstly I am using this for the ListView control itself:
ItemsSource="{Binding AllEffects}"
where 3 GridViewColumns already binded to to AllEffects.
But I have 2 more GridViewColumns that I want to bind to a separate static property found in:
public static class AllSupport
{
public static EffectSupportLookup<HardwareType, List<EffectSupport>>
}
public class EffectSupport
{
public bool IsSupported {get;set;}
}
I have tried this:
<GridViewColumn
Width="Auto"
Header="GPU">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Margin="0"
HorizontalAlignment="Center"
IsChecked="{Binding AllSupport, Mode=TwoWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
But at runtime, it complains that the there is no property called AllSupport on AllEffects. I don't want to store it inside AllEffects because this is a separate class already compatible with the UI, so I just want to bind it to:
AllSupport.EffectSupport[GPU].IsSupported
Any ideas?
Use x:Static Markup Extension.
Something like (never tested):
<Window xmlns:local="AssemblyName">
<ItemsControl
ItemsSource="{Binding Source={x:Static local:AllSupport.EffectSupport}}" />
</Window>
Or thru to a CollectionViewSource.
Update:
You have to specify internal path
Are you sure the local xmlns points to the right clr namespace (get assistance from the VS Xaml Intellisense)
Does your app compile before setting it in xaml?
Be more specific with your generic class implmenetation, what are you trying to achieve? is it a dictionary? a generic class? please reedit your code to give us the right look of your scenario.
I have 2 more GridViewColumns that I want to bind to a separate static property
I don't think you can do that, 1 ItemsSource means 1 SourceCollection.
But you can easily use LINQ to create a ad-hoc collection that includes those 2 columns
I don't want to store it inside AllEffects because this is a separate class already compatible with the UI,
If this means it's a ViewModel class, and if you want those columns in the View, then that is a very strong reason they should be stored in that class. Or in a separate, derived, ViewModel.