Data templates are great, but I'm having a problem with binding in a particular situation. I have a class, Value, that has various descendants like StringValue, DateValue, etc. These Values show up in a Listbox. This template works fine, binding to a specific property of StringValue:
<DataTemplate DataType="{x:Type values:StringValue}">
<TextBox Margin="0.5"
Text="{Binding Path=Native}" />
</DataTemplate>
However, when I bind to an object itself, instead of a specific property, the changes don't update the object, as in this template:
<DataTemplate DataType="{x:Type values:LookupValue}">
<qp:IncrementalLookupBox SelectedValue="{Binding Path=., Mode=TwoWay}"
LookupProvider="{Binding ElementName=EditWindow, Path=ViewModel.LookupProvider}">
</qp:IncrementalLookupBox>
</DataTemplate>
IncrementalLookupBox is a UserControl that ultimately allows a user to select a LookupValue, which should replace the item bound in the template. If this was bound to a simple type like an int or string, the binding would replace the object, so I'm not sure what the difference is with a more complex object. I know that the IncrementalLookBox is working, because binding some textboxes to the properties of SelectedValue (which is a dependency property) shows the correctly selected LookupValue.
In case it makes the situation more clear, here is the implementation of SelectedValue:
public LookupValue SelectedValue
{
get { return (LookupValue)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(LookupValue), typeof(IncrementalLookupBox), new PropertyMetadata(OnSelectedValuePropertyChanged));
private static void OnSelectedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as IncrementalLookupBox;
obj.OnSelectedValuePropertyChanged(e);
}
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
}
If all else fails consider using a ValueConverter to get the value you require.
Edit: this does not work. See link in comments below.
Make sure your class implements INotifyPropertyChanged and raise PropertyChanaged here:
private void OnSelectedValuePropertyChanged(DependencyPropertyChangedEventArgs e)
{
CheckForSelectedValueInLookups();
// RaisePropertyChanged();
}
My issue is the same as described here:
WPF TwoWay Binding of ListBox using DataTemplate
Apparently if I don't write enough text here, my answer will be converted to a comment and not close out the question. So, to summarize the issue, a two-way Binding=. in a datatemplate used in a ListBox (or any ItemsControl I image) won't work, because it is not the object itself being bound, but the ListBoxItem that contains it.
Related
I have two user controls, one contains a TreeView, one contains a ListView.
The TreeView has an itemsource and hierarchical data templates that fill the nodes and leafes (node=TvShow, leaf=Season).
The ListView should show the children of the selected TreeView item (thus, the selected season): the episodes of that season.
This worked fine when I had both the TreeView and the Listview defined in the same window, I could use something like this:
<ListView
x:Name="_listViewEpisodes"
Grid.Column="2"
ItemsSource="{Binding ElementName=_tvShowsTreeView, Path=SelectedItem.Episodes}">
How can I achieve this, when both controls are defined in separate user controls? (because in the context of one user control, I miss the context of the other user control)
This seems something pretty basic and I am getting frustrated that I can't figure it out by myself. I refuse to solve this with code-behind, I have a very clean MVVM project so far and I would like to keep it that way.
Hope that somebody can give me some advise!
First of all you have to created the SelectedValue proeprty in your ViewModel and bind the TreeView.SelectedItem property to it. Since the SelectedItem property is read-only I suggest you to create a helper to create OneWayToSource-like binding. The code should be like the following:
public class BindingWrapper {
public static object GetSource(DependencyObject obj) { return (object)obj.GetValue(SourceProperty); }
public static void SetSource(DependencyObject obj, object value) { obj.SetValue(SourceProperty, value); }
public static object GetTarget(DependencyObject obj) { return (object)obj.GetValue(TargetProperty); }
public static void SetTarget(DependencyObject obj, object value) { obj.SetValue(TargetProperty, value); }
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null));
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached("Source", typeof(object), typeof(BindingWrapper), new PropertyMetadata(null, OnSourceChanged));
static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
SetTarget(d, e.NewValue);
}
}
The idea is simple: you have two attached properties, the Source and the Target. When the first one changes the PropertyChangedCallback is called and you simply setting the NewValue as the Target property value. In my opinion this scenario is helpful in a lot of cases when you need to bind the read-only property in XAML (especially in control templates).
I've created a simple model to demonstrate how to use this helper:
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.values = new ObservableCollection<string>()
{
"first",
"second",
"third"
};
}
ObservableCollection<string> values;
string selectedValue;
public ObservableCollection<string> Values { get { return values; } }
public string SelectedValue {
get { return selectedValue; }
set {
if (Equals(selectedValue, values))
return;
selectedValue = value;
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, we have data source, selected value and we'll bind it like this:
<StackPanel>
<TreeView ItemsSource="{Binding Values}"
local:BindingWrapper.Source="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Mode=OneWay}"
local:BindingWrapper.Target="{Binding SelectedValue, Mode=OneWayToSource}"
>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Header" Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<TextBlock Text="{Binding SelectedValue}"/>
</StackPanel>
In the TreeView bound to the ItemsSource from the ViewModel I've created two bindings so they are changing the SelectedValue property in your ViewModel. TextBlock in the end of the sample is used just to show that this approach works.
About the very clean MVVM - I think that it is not the same as the "no code-behind". In my sample the ViewModel still doesn't know anything about your view and if you'll use another control to show your data e.g. ListBox you will be able to use the simple two-way binding and the "BindingWrapper" helper will not make your code unreadable or unportable or anything else.
Create a SelectedSeason property in your ViewModel and bind the ListView's ItemsSource to SelectedSeason.Episodes.
In a perfect world, you could now use a Two-Way binding in the TreeView to automatically update this property when the SelectedItem changes. However, the TreeView's SelectedItem property is readonly and cannot be bound. You can use just a little bit of code-behind and create an event handler for the SelectionChanged event of the TreeView to update your ViewModel's SelectedSeason there. IMHO this doesn't violate the the MVVM principles.
If you want a pure XAML solution, that a look at this answer.
I have two user-controls: a LocationTreeView, and a LocationPicker. The LocationTreeView organizes Locations into a tree structure. Because of the number of locations involved, only parts of the tree are loaded at once (one level at a time as items are expanded).
The LocationPicker is little more than a textblock with a button that opens a modal window with a LocationTreeView on it.
When I bind my LocationPicker's "SelectedLocation" property to my Viewmodel, it works fine. When I bind my LocationTreeView to the viewmodel, the binding doesn't seem to have any effect at all. When I bind my LocationTreeView to a "dummy" LocationPicker (which is bound to my viewmodel) it works.
How can I get my LocationTreeView to bind to my viewmodel?
public partial class LocationTreeView: UserControl
{
public EventHandler LocationChanged;
...
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",typeof(Location), typeof(LocationTreeView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedLocationChanged));
...
public static void SelectedLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LocationTreeView sender = (d as LocationTreeView);
Location loc = e.NewValue as Location;
//Navigate the treeview to the selected location
sender.LoadLTreeViewPathToLocation(loc);
}
public Location SelectedLocation
{
get { return (Location)GetValue(SelectedLocationProperty); }
set
{
if (SelectedLocation != value)
{
SetValue(SelectedLocationProperty, value);
if (LocationChanged != null)
{
LocationChanged(this, EventArgs.Empty);
}
}
}
}
...
}
Binding on this control works fine when bound to another control, but not when bound to my viewmodel. I've set a breakpoint in the SelectedLocationChanged callback, it doesn't seem to get fired when I set the viewmodel property (which DOES implement INotifyPropertyChanged)
public partial class LocationPicker: UserControl
{
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",typeof(Location), typeof(LocationPicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
...
public Location SelectedLocation
{
get { return (Location)GetValue(SelectedLocationProperty); }
set { SetValue(SelectedLocationProperty, value); }
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
// create a window with a locationtreeview on it. Set the treeview's
// selectedlocation property, open the window, wait for the window to close,
// set this.SelectedLoctation to the treeview's selected location.
}
}
I apologize for the leaving out so much code. My work enviroment prevents me from being able to copy/paste.
I've left out the code for the ViewModel. I am quite confident that it is not the issue.
Update:
The LocationTreeView has a ViewModel that is set in the xaml
<UserControl.DataContext>
<VM:LocationTreeViewViewModel />
</UserControl.DataContext>
The LocationPicker does not have a ViewModel.
On the window that I am using the controls, the xaml looks something like this
<Widow.DataContext>
<VM:TestWindowViewModel />
</Window.DataContext>
<Grid>
...
<UC:LocationPicker x:Name="picker" SelectedLocation="{Binding Location}" />
<!-- this does not work -->
<UC:LocationTreeView SelectedLocaiton="{Binding Location}" />
<!-- but this works --->
<UC:LocationTreeView SelectedLocaiton="{Binding SelectedLocation, ElementName=picker}" />
...
</Grid>
If you want to data bind from your view model to the LocationTreeView, then you should use the property in the view model to data bind to. If your view model had a property named SelectedLocationInViewModel in it, then you should use that to data bind to:
<UC:LocationTreeView SelectedLocation="{Binding SelectedLocationInViewModel}" />
I think that I see what your problem is now... you want to define some properties in the UserControl and data bind to them, but also data bind to properties from the view model that is set as the DataContext. You need to use a RelativeSource Binding to do that... just look at the Binding Paths in these examples:
To data bind to properties declared in a UserControl from within the UserControl:
<ItemsControl ItemsSource="{Binding PropertyName, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
To data bind to properties declared in any object set as the DataContext:
<ItemsControl ItemsSource="{Binding PropertyName}" />
Simple enough requirement - trying to reset a WPF combobox on user press of a "clear" button. Everything else on the form clears as expected, with the exception of this ComboBox.
<ComboBox ItemsSource="{Binding Members}" DisplayMemberPath="MemberName" SelectedValue="{Binding RequestingMember, Mode=TwoWay}" SelectedValuePath="MemberID" IsEditable="{Binding FixedRequestingMember }"></ComboBox>
Here's the property it's bound to:
public int RequestingMember
{
get { return _requestingMember; }
set
{
if (_requestingMember != value)
{
_requestingMember = value;
}
}
}
And here's what I'm using to clear the box:
this.RequestingMember = -1;
Worth mentioning that there's nothing in the Members collection which corresponds to a key of -1. The value doesn't change from its selection when you press clear, anyway.
I've tried setting the int to 0 and also setting UpdateSourceTrigger=PropertyChanged in the XAML, to no avail. I'm loathe to change RequestingMemeber to a type of int? as it'll need fixes that cascade a long way into other parts of the application.
What am I doing wrong?
Please read the Use SelectedValue, SelectedValuePath, and SelectedItem page on MSDN for the full information on this, but in short, you will have more luck by data binding to the SelectedItem property, rather than using the SelectedValue and SelectedValuePath properties. Try adding a property of the same type as the items in the collection and data binding that to the SelectedItem property instead:
public Member SelectedMember // Implement the INotifyPropertyChanged Interface here!!
{
get { return selectedMember; }
set
{
if (selectedMember != value)
{
selectedMember = value;
NotifyPropertyChanged("SelectedMember");
}
}
}
You will also need to implement the INotifyPropertyChanged Interface in your class with the properties. Your XAML should now look like this:
<ComboBox ItemsSource="{Binding Members}" DisplayMemberPath="MemberName"
SelectedItem="{Binding SelectedMember, Mode=TwoWay}"
IsEditable="{Binding FixedRequestingMember }" />
Now all you need to do to clear the selection is this:
SelectedMember = null;
I'm having an issue with a binding that I'm trying to implement. It will update the DP once, but after that, it's never updated again.
In XAML I have two controls binding to a listview.selected item.
<controls:MapControl DataContext="{Binding ElementName=availableMapsListView, Path=SelectedItem}" MapData="{Binding .}">
and
<TextBlock DataContext="{Binding ElementName=availableMapsListView, Path=SelectedItem}" Text="{Binding Name}" />
The textblock update as expected with each change of the listview's selected item.
My custom control creates the dependency property like so:
public class MapControl : UserControl
{
public MapData MapData
{
get { return (MapData)GetValue(MapDataProperty); }
set { SetValue(MapDataProperty, value); }
}
public static readonly DependencyProperty MapDataProperty =
DependencyProperty.Register("MapData", typeof(MapData), typeof(MapControl),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnMapDataPropertyChanged),
new CoerceValueCallback(OnMapCoerceValue)
)
);
private static void OnMapDataPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((MapControl)source).MapData = (MapData)e.NewValue;
}
}
private static object OnMapCoerceValue(DependencyObject dpo, Object obj)
{
return obj;
}
...
}
I'm pretty much at my wits end and not sure what I should do from here. Any help is greatly appreciated.
Not sure exactly what you're trying to achieve or why your code appears so convoluted. If you explain more someone may be able to provide you with a much simpler solution.
That said, by the sounds of it the problem is simply that you're overwriting the binding with a local value. This looks like the culprit:
((MapControl)source).MapData = (MapData)e.NewValue;
When you do this, the MapControl.MapData property will no longer be bound to '.' Instead, it will take on whatever value you've assigned. So your MapControl.DataContext property is likely perfectly correct, but it's not being transferred to the MapData property because you've destroyed the binding.
I had the same error last week. My solution was simple : When you explicitly define a DependencyProperty you must also explicitly define the mode to TwoWay.
<TextBlock DataContext="{Binding ElementName=availableMapsListView,
Path=SelectedItem}"
Text="{Binding Name, Mode=TwoWay}" />
I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.