I have a multiselect Combobox usercontrol and a dependency property 'SelectedItems'.
I m trying to use the usercontrol and bind the 'SelectedItems' to another property called 'SelectedResultItems' in my ViewModel. But I dont get any values to SelectedResultItems. Please help
Here is what i tried.
My main xaml:
<DataTemplate x:Key="TypeATemplate">
<control:MultiSelectComboBox Width="315" ItemsSource="{Binding
ResultvalueList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding
SelectedResultItems,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
My Combobox usercontrol code behind:
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems",
typeof(ObservableCollection<string>), typeof(MultiSelectComboBox), new
FrameworkPropertyMetadata(null,new
PropertyChangedCallback(MultiSelectComboBox.OnSelectedItemsChanged)));
public ObservableCollection<string> SelectedItems
{
get { return
(ObservableCollection<string>)GetValue(SelectedItemsProperty); }
set
{
SetValue(SelectedItemsProperty, value);
}
}
I am setting the 'SelectedItems' on click of the checkbox.
My mainviewmodel:
public ObservableCollection<string> SelectedResultItems
{
get => _selectedResultItems;
set
{
_selectedResultItems = value;
NotifyPropertyChanged(nameof(SelectedResultItems));
}
}
If this is the same as for ListView(never used MultiSelectCombobox), you cannot bind to SelectedItems because it is a read-only property.
What I did to solve that is add the event SelectionChanged to ListView(or MultiSelectCombobox for you).
Then event would be :
private void YourComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
contexte.ResultItems = YourComboBox.SelectedItems.Cast<YourItem>().ToList();
}
Maybe there is a different way to do it, but until now that's the easiest way I found.
Related
I have a view which wraps a TreeView, called MbiTreeView. I want to get the selected item from the (wrapped) tree view in the view model.
The 'parent' user control which uses this custom user control:
<UserControl [...]>
<views:MbiTreeView
Grid.Row="0"
cal:Bind.Model="{Binding TreeViewModel}"
SelectedItem="{Binding SelectedItem}">
</views:MbiTreeView>
</UserControl>
The parent user control is bound to this view model:
internal sealed class SomeViewModel : PropertyChangedBase
{
public object SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
NotifyOfPropertyChange(() => SelectedItem);
}
}
public IMbiTreeViewModel TreeViewModel { get; }
public SomeViewModel(
IMbiTreeViewModel treeViewModel)
{
TreeViewModel = treeViewModel;
}
}
The MbiTreeView user control is rather straight forward. It subscribes to the selection changed event, and defines a few templates (not relevant for this question, so left them out in the question)
<TreeView ItemsSource="{Binding Items}" SelectedItemChanged="TreeView_OnSelectedItemChanged">
iew.ItemContainerStyle>
The code behind declares the dependency property:
public partial class MbiTreeView
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
null);
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
public MbiTreeView()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
}
}
when I start the application, I can navigate through the tree view items. When I click on a treeview node, then the OnSelectedItemChanged event fires (I get into my breakpoint there). So everything goes fine up and until setting the value in the dependency property SelectedItem.
Then I would expect that the xaml binding gets notified, and updates the view model. But that never happens.
I am getting nowhere with this, help is greatly appreciated.
The SelectedItem Binding should be TwoWay:
<views:MbiTreeView ...
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
You could declare the property like shown below to make to bind TwoWay by default.
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem),
typeof(object),
typeof(MbiTreeView),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I have a UserControl that contains a ListBox and I want to track the SelectedItems of that listbox.
The UserControl has a DP "SelectedItemsList" that is defined like this
public static DependencyProperty SelectedItemsListProperty = DependencyProperty.Register(
"SelectedItemsList",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null,
OnSelectedItemsChanged));
In the listbox' Item "SelectionChanged" event, I want to save the selected items to the DP. This is triggered whenever I change the selection in the listbox.
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItemsList = this.myListBox.SelectedItems;
}
In my view that contains the "MyListControl" I create a binding to my viewmodel that want to use the selected items.
<controls:MyListControl
Source="{Binding SomeItemsList, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding SelectedItems, UpdateSourceTrigger=PropertyChanged}"/>
My problem is, that the DP SelectedItemsList never gets updated. The PropertyChangeCallback "OnSelectedItemsChanged" of the DP is only triggered when I initially load the lists content. The value of the SelectedItemsList is always null.
I am aware that this question is similar to Dependency property callback does not work, but the answers posted there do not solve my problem.
What am I missing here?
Thanks,
Edit (2015-09-10):
Thank you all for your comments. I found a solution that fits my needs:
First of all I created a custom listbox control that provided the list of selected items in a dependency property (very similar to Select multiple items from a DataGrid in an MVVM WPF project).
public class CustomListBox : ListBox
{
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList",
typeof (IList),
typeof (CustomListBox),
new PropertyMetadata(null));
public CustomListBox()
{
SelectionChanged += OnSelectionChanged;
}
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList= new ArrayList(this.SelectedItems);
}
}
I am not happy yet with the "new ArrayList"-part, but if in my viewmodel's property setter I want to check for equality, SelectedItemsList can not be a reference of SelectedItems. The previous and the new value would always be the same.
Then I reduced the item selection parts of my UserControl "MyListControl" simply to the dependency property itself:
public static DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
"SelectedItems",
typeof (IList),
typeof (MyListControl),
new FrameworkPropertyMetadata(null));
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
set
{
SetValue(SelectedItemsProperty, value);
}
}
and modified the xaml of the MyListControl:
<controls:CustomListBox
SelectionMode="Extended"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=Source, UpdateSourceTrigger=PropertyChanged}"
SelectedItemsList="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:MyListControl}},
Path=SelectedItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
The property in my ViewModel looks like
public IList SelectedObjects
{
get { return _selectedObjects; }
set { if (this._selectedObjects != value)
{
this._selectedObjects = value;
OnPropertyChanged(SelectedObjectsProperty);
}
}
}
It was important that the type of this property is IList, otherwise the value in the setter would always be null.
And in the view's xaml
<controls:MyListControl
Source="{Binding CurrentImageList, UpdateSourceTrigger=PropertyChanged}"
SelectedItems="{Binding SelectedObjects, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
I just had the same problem today, unfortunately, when you are assigning to SelectedItemsList a value, WPF seems to unbind it. To fix it, I update the value in the binded item. I know that it is not the best solution in the world but for me it works.
In this case the code would looked like this:
private void OnItemSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SetPropertyValue(
this.GetBindingExpression(SelectedItemsListProperty),
this.myListBox.SelectedItems);
}
private void SetPropertyValue(BindingExpression bindingExpression, object value)
{
string path = bindingExpression.ParentBinding.Path.Path;
var properties = new Queue<string>(
path.Split(
new[]
{
'.'
}).ToList());
this.SetPropertyValue(bindingExpression.DataItem, bindingExpression.DataItem.GetType(), properties, value);
}
private void SetPropertyValue(object destination, Type type, Queue<string> properties, object value)
{
PropertyInfo property = type.GetProperty(properties.Dequeue());
if (property != null && destination != null)
{
if (properties.Count > 0)
{
this.SetPropertyValue(property.GetValue(destination), property.PropertyType, properties, value);
}
else
{
property.SetValue(destination, value);
}
}
}
You need to bind your Listbox' SelectedItems to the DP SelectedItemsList to propagate the user selection to the DP. The binding you already have will then pass the changes on to the viewmodel, but I think you will need a binding mode 'twoway' instead of UpdateSourceTrigger.
And don't use the PropertyChangeCallback in your DP: Changing the SelectedItemsList if the SelectedItemsListProperty has changed makes no sense. (Usually the former is a wrapper property of the latter.)
I have created a custom control CustomTextBox inherited from TextBox class. I have created a dependency property named CustomTextProperty.
I have binded this DP with my Viewmodel property.
While Registering the DP i have given the property change callback but it is only get called one time when my control gets the binded data initially when my xaml loads.
When i try to set my control from view the binded VM property setter does not gets called and also the propertychangecallback not gets fired.
Please help!!
Code snipet below:
My Custom control
class CustomTextBox : TextBox
{
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register("CustomText",
typeof(string), typeof(CustomTextBox),
new FrameworkPropertyMetadata("CustomTextBox",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnCustomPropertyChange)));
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
private static void OnCustomPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This is Demo Application.
// Code to be done Later...
}
}
My View Model:
public class ViewModel : INotifyPropertyChanged
{
private string textForTextBox;
public string TextForCustomTextBox
{
get
{
return this.textForTextBox;
}
set
{
this.textForTextBox = value;
this.OnPropertyChange("TextForCustomTextBox");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
My Xaml Code with Binding:
<custom:CustomTextBox x:Name="CustomTextBox"
CustomText="{Binding TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
My Code Behind to set DataContext:
// My View Constructor
public View1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
You said that you declared a CustomText DependencyProperty and data bound it to your view model TextForCustomTextBox property and that much is correct. However, when you said that you tried to set your property from the view, you were mistaken.
What you were actually doing was setting the CustomTextBox .Text property from the view and that wasn't connected to your CustomTextBox.CustomText property. You can connect them like this, although I'm not quite sure what the point of that would be:
<Views:CustomTextBox x:Name="CustomTextBox" Text="{Binding CustomText, RelativeSource={
RelativeSource Self}, UpdateSourceTrigger=PropertyChanged}" CustomText="{Binding
TextForCustomTextBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1" HorizontalAlignment="Center" Width="200" Height="50" />
Try setting your DataContext BEFORE the actual initialization so it is available when the form/control objects are created. If it can't find before, is that what may be causing the failed bindings.
This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solution I have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Several other questions are asked about this, but no other working solutions are given.
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
You should not really need to deal with the SelectedItem property directly, bind IsSelected to a property on your viewmodel and keep track of the selected item there.
A sketch:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which
// also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree,
// otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some SelectedSomething property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource;
Listen to the SelectedItemChanged in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
You can create an attached property that is bindable and has a getter and setter:
public class TreeViewHelper
{
private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView))
return;
if (!behaviors.ContainsKey(obj))
behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
TreeViewSelectedItemBehavior view = behaviors[obj];
view.ChangeSelectedItem(e.NewValue);
}
private class TreeViewSelectedItemBehavior
{
TreeView view;
public TreeViewSelectedItemBehavior(TreeView view)
{
this.view = view;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
}
internal void ChangeSelectedItem(object p)
{
TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
item.IsSelected = true;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
Use the OneWayToSource binding mode. This doesn't work. See edit.
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support
bindings on properties that are not DependencyProperties. The runtime
per-instance state of a binding is held in a BindingExpression, which
we store in the EffectiveValueTable for the target DependencyObject.
When the target property is not a DP or the DP is read-only, there's
no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality
to these two scenarios. We get asked about them pretty frequently. In
other words, your request is already on our list of features to
consider in future releases.
Thanks for your feedback.
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.
I have a binding problem in silverlight and I cannot make it work...
I have a control which has a model behind.
The control code behind looks like this
.....
public ClientListingVM()//constructor
{
ClientListingViewModel model = new ClientListingViewModel();//this is my model
DataContext = model;
InitializeComponent();
}
.....
the model code behind looks like this
....
public ObservableCollection<FilterItemDefinition> FilterItemDefinitions
{
get { return _filterItemDefinitions; }
set
{
if (_filterItemDefinitions != value)
{
_filterItemDefinitions = value;
}
OnPropertyChanged("FilterItems");
}
}
....
In the control xaml I have this
....
<my:Filters Height="150" FilterItems="{Binding Path=FilterItemDefinitions}" Columns="{Binding ElementName=ClientGrid, Path=Columns}"/>
....
where Filters is a user control...
Until here everything works ok...
The code behind for filters looks like this
....
public partial class Filters : UserControl
{
public Filters()
{
InitializeComponent();
}
private DependencyProperty FilterItemsProperty = DependencyProperty.Register("FilterItems", typeof(ObservableCollection<FilterItemDefinition>), typeof(Filters), new PropertyMetadata(null, new PropertyChangedCallback(OnChangef)));
public ObservableCollection<FilterItemDefinition> FilterItems
{
get
{
return (ObservableCollection<FilterItemDefinition>)GetValue(FilterItemsProperty);
}
set
{
SetValue(FilterItemsProperty, value);
}
}
private DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(ObservableCollection<DataGridColumn>), typeof(Filters), new PropertyMetadata(null, new PropertyChangedCallback(OnChangec)));
public ObservableCollection<DataGridColumn> Columns
{
get
{
return (ObservableCollection<DataGridColumn>)GetValue(ColumnsProperty);
}
set
{
SetValue(ColumnsProperty, value);
}
}
public static void OnChangef(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public static void OnChangec(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
....
and the xaml looks like this
....
<ListBox x:Name="myListBox" ItemsSource="{Binding Path=FilterItems, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<my:FilterItem Columns="{Binding Source={StaticResource DataContextProxy},Path=DataSource.Columns}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
....
So here is the problem:
the ListBox ItemSource is bound to FilterItems... but the Filters DataContext is ClientListingViewModel... and that model does not have FilterItems property.
If in the Filters constructor I set a FiltersViewModel or I say DataContext=this the Filters DataContext will have the property FilterItems and the binding willwork ok.. But the property FilterItemDefinitions from ClientListingViewModel will not get to my control...
I would like that the DataContext for FiltersControl to be his DataContext or the code behind.. but in the same time the binding of his properties with the parent control model to work..
I am sure this is possible and I am doing it in the wrong way.. can someone please help me ...
10x
Leave your existing DataContext alone, you don't want to be doing things like DataContext = this. Instead bind ItemsSource like this:-
ItemsSource="{Binding Path=Parent.FilterItems, ElementName=LayoutRoot}"
This assumes you have default root element named as LayoutRoot. Hence the Parent of this root will be your Filters user control which has a FilterItems property. BTW Two way binding on this property doesn't make a lot of sense.