How to bind a property in itemtemplate to a proptery in itemsource? - wpf

Consider this container:
public class ItemInfo : DependencyObject
{
public string Name { get; set; }
public ObservableCollection<SomeDataItem> DataValues { get; set; }
...
Dependency object registration and event handling
...
}
public class MyItemSource : ObservableCollection<ItemInfo>
{
...
}
Now, I wish to display this data in a listview where the control that displays the item is custom. For that, I'd set the MyItemSource to listview's ItemSource and define a ItemTemplate. However, it seems that I have no access to ItemInfo in the ItemTemplate. This is my XAML:
<Grid>
<ListBox ItemsSource="{StaticResource MyStaticDataSource}"
Grid.IsSharedSizeScope="True">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ItemInfoUserControl x:Name="itemInfoUserControl"
Name = "{Binding Name}" <--- this doesn't work
Data = "{Binding DataValues}" <--- this doesn't work
Width="300" Height="200"
Grid.Row="1">
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Instead of binding to ItemSource's ItemInfo, it binds to the ItemInfoUserControl properties, which is not what I wish it to do. Is there a way to bind properties in itemtemplate to a property in itemsource ? Or is there an alternative approach to what i'm ultimately trying to accomplish ?
Thanks!

Well, first of all you need to use proper binding syntax:
Name = "{Binding Name}"
and
Data = "{Binding DataValues}"
Instead of just "Name = "Binding Name"" and "Data = "Binding DataValues"". Note the addition of "{" and "}" around your binding expression.
This might be enough to solve your problem, as long as Name and DataValues are DependencyProperties in ItemInfoUserControl. If not, you'll need to implement them as DependencyProperties in order to be able to bind to them in XAML. See here for a good MSDN article on defining custom dependency properties.
Edit: Also, just noticed -- you're setting both x:Name and Name. From this article on MSDN:
If Name is available as a property on the class, Name and x:Name can be used interchangeably as attributes, but a parse exception will result if both are specified on the same element. If the XAML is markup compiled, the exception will occur on the markup compile, otherwise it occurs on load.
Remove x:Name="itemInfoUserControl" and see if that helps.

Related

WPF DataTemplate with Binding

A WPF ComboBox/ListBox itemtemplate/datatemplate question please.
Let's say that I set DisplayMember="Name", this is equivalent to
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
"Name" is a field in my view model.
Now, if DisplayMember="{Binding Name}", "Name" is no longer a property in my view model but instead contains the name of the property in my view model that I want to display. Using an ItemTemplate, how would I set this up? Thank you in advance.
DisplayMemberPath is a property of the ItemsControl. And its binding path will be resolved relative to the ItemsControl's DataContext, not its items.
Example (pseudo code without INPC interface implementation):
public class ViewModel
{
// This sets the string name of the property (or the path to it)
// to be displayed in the items.
public string DisplayNameProperty {get; set;} = "Name" // Or = "Surname"
// Items Source
public IEnumerable Items {get; set;}
}
<ItemsControl ItemsSource="{Binding Items}"
DisplayMemberPath="{Binding DisplayNameProperty}"/>
The binding in the DisplayMemberPath property will allow you to control from the ViewModel which property will be displayed in ItemsControl's items.
But in practice, such a task is extremely rare.
Personally, in my practice, such a need has never arisen.

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

Make only one of many controls visible based on value of another control

I have a ComboBox bound to a collection of objects defined as this.
public class TierOption
{
public string Option { get; set; }
public Type DataType { get; set; }
}
public class TierOptions : ObservableCollection<Tier1Option>
{
}
I have 3 other controls related to this ComboBox, which are a TextBox, ComboBox, or a WPFToolKit:DatePicker.
I need to show only the related control which corresponds to the datatype(Type) of the object selected in the first ComboBox and neither of the others.
Pseudo Code Example:
(Probably too close to butchered C# but hopefully it conveys the idea)
switch (ComboBox.SelectedItem.DataType)
{
case String:
TextBox.Visibility = Visibility.Visible;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Hidden;
break;
case DateTime:
TextBox.Visibility = Visibility.Hidden;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Visible;
break;
<...so forth and so on...>
}
My attempts have resulted in very non-wpf looking convoluted messes which don't work regardless. Being new to wpf I'm trying very hard to stay true to the best design practices.
Thank you!
You can play with DataTemplate with DataType property
<...Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type DateTime}">
<DatePicker .../>
</DataTemplate>
...
</...Resources>
<ContentControl Content="{Binding SelectedItem, ElementName=myComboBox}"/>
<ComboBox ItemsSource="{Binding ...}"/>
The code above is just the idea, you could have to make adjustements. For example you won't be able to modify a string item itself (you could have to encapsulate each item of your list)
If your list contains all items of the same type, you can use a ContentTemplateSelector on contentControl.
ContentControl Content="{Binding SelectedItem, ElementName=YourCombBox}" ContentTemplateSelector="{StaticResource YourTemplateSelector}"
MSDN DOC about ContentControl.ContentTemplateSelector Property
Bind to the detail visibility to ElementName=ComboBox Path=SelectedItem.DataType. And you will need to use a converter that returns visibility. You will need two converters return opposite answers. If you have more than 2 combination then some more in the line of Jonas.
I assumed Type was a system class and it appears to be a custom class. Extend that class to have additional properties. Even if Type was a system type you could just create a class that implements it and extend it.
public Visibility TextBoxVisibility { get; }
public Visibility ComboBoxVisibility { get; }
...
Then on TextBox bind the visibility
Visisbility="{binding ElementName=Combobox Path=SelectedItem.DataType.TextBoxVisibility]";

wpf binding from a FindAncestor to Dependency Property of custom control

I've got a custom WPF control with a DependencyProperty MyString
I'm using the control within an ItemsControl on my View and want to fish a value out from the ViewModel.
As the DataContext of the control becomes each Item in the ItemsSource of the ItemsControl I thought I'd just be able to use FindAncestor but it dosnt seem to work ... can anyone see where I'm going wrong please?
Heres the XAML on the View ...
<Grid>
<ItemsControl ItemsSource="{Binding MyItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="myStack">
<ImportExceptions:ControlStrip MyString="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.MyStringOnViewModel}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and heres the code behind my custom control where I've set up my dependency property ...
public partial class ControlStrip
{
public ControlStrip()
{
InitializeComponent();
}
public string MyString
{
get
{
return GetValue(MyStringProperty).ToString();
}
set
{
SetValue(MyStringProperty, value);
}
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.RegisterAttached("MyString", typeof (string), typeof (ControlStrip));
}
The DataContext of the control doesn't change - the DataContext for the ImportExceptions:ControlStrip will be (unless explicitly specified) the next DataContext available as its goes 'up' the visual tree...
I infer from your code that you have set the DataContext of the View to a ViewModel with properties 'MyItems' and 'MyStringOnViewModel' - you should be able to simply bind the MyString property directly to the ViewModel, like
<ImportExceptions:ControlStrip MyString="{Binding Path=MyStringOnViewModel}" />
Your code looks fine. Probably you have made an error in the DataContext reference. In all likeliness the DataContext of the the ItemsControl already is MyStringOnViewModel. So, omit the .MystringOnViewModel after the DataContext in the Path attribute. If not can you give some more code, ore post a simplification of it that mimicks how the DataCon,text(s) is/are set?

Can I apply a ContextMenu to a ContextMenuViewModel using a DataTemplate?

I have a ViewModel (AbstractContextMenu) that represents my context menu (IContextMenu), and I bind a real ContextMenu to it with a DataTemplate:
<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=(local:IContextMenu.Items)}"
IsEnabled="{Binding Path=(local:IContextMenu.IsEnabled)}"/>
</DataTemplate>
Then I have a dummy ConcreteContextMenu for testing that just inherits from AbstractContextMenu. AbstractContextMenu just implements this interface:
public interface IContextMenu : IExtension
{
IEnumerable<IMenuItem> Items { get; set; }
bool IsEnabled { get; set; }
}
I'm using it as a property of another ViewModel object:
public IContextMenu ContextMenu
{
get
{
return m_ContextMenu;
}
protected set
{
if (m_ContextMenu != value)
{
m_ContextMenu = value;
NotifyPropertyChanged(m_ContextMenuArgs);
}
}
}
private IContextMenu m_ContextMenu = new ConcreteContextMenu();
static readonly PropertyChangedEventArgs m_ContextMenuArgs =
NotifyPropertyChangedHelper.CreateArgs<AbstractSolutionItem>(o => o.ContextMenu);
Then I bind a StackPanel to that ViewModel and bind the ContextMenu property on the StackPanel to the ContextMenu property of the ViewModel:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<!-- stuff goes in here -->
</StackPanel>
When I run this, the ContextMenuOpening event on the StackPanel is fired, but the ContextMenu is never displayed. I'm not sure if I can even do this (apply a ContextMenu to a ContextMenu ViewModel using a DataTemplate). Anyone know?
What is the type of AbstractSolutionItem.ContextMenu? If it corresponds to the ContextMenu property in your question, then the problem could be that the type is wrong. The ContextMenu property of FrameworkElement is expecting an actual ContextMenu, not an IContextMenu. Try checking the output window while debugging your app - you might get an error message stating that this is the problem.
Instead of using a DataTemplate to define your ContextMenu, just put the contents of the template StackPanel.ContextMenu:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<StackPanel.ContextMenu DataContext="{Binding Path=(local:AbstractSolutionItem.ContextMenu)}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=Items}"
IsEnabled="{Binding Path=IsEnabled}"/>
</StackPanel.ContextMenu>
<!-- stuff goes in here -->
</StackPanel>
That should get you most of the way there. However, there is still a problem since the ContextMenu does not know how to create a MenuItem from an IMenuItem. To solve this, create an ItemTemplate for the ContextMenu, which binds members of IMenuItem to `MenuItem.
Could you shed some light on the syntax used in the ItemsSource property in the DataTemplate ? Using parentheses usually means an attached property. And Items does not seem to be an attached property defined by IContextMenu (as an interface cannot define such a property).
The DataTemplate is linked to an object of type AbstractContextMenu which has a property called Items. So, the DataTemplate could simply reference it like this:
<DataTemplate DataType="{x:Type local:AbstractContextMenu}">
<ContextMenu x:Name="contextMenu"
ItemsSource="{Binding Path=Items)}"
IsEnabled="{Binding Path=IsEnabled}"/>
</DataTemplate>
If the AbstractSolutionItem class is the VM of the StackPanel, you could bind it like this:
<StackPanel Orientation="Horizontal"
ContextMenu="{Binding Path=ContextMenu}"
ContextMenuOpening="stackPanel_ContextMenuOpening">
<!-- stuff goes in here -->
</StackPanel>
Of course, the DataTemplate must be "accessible" from the StackPanel.
Bind the ContextMenu property of your view (StackPanel in this scenario) to the ContextMenu property of your ViewModel and provide a IValueConverter to the binding that will create the ContextMenu object and set the IContextMenu to it's DataContext.

Resources