I have a View A that has a UserControl U containing its own UserControl UChild.
When I set the DataContext of A, will UChild get the DataContext of A using direct binding to its grandparent's property i.e. { Binding PropertyFromA } within UChild?
I am trying to achieve this but looks like grandchild in my case does not get the DataContext.
Edit:
Some code I use:
My binding happens inside the VM, where BookList is an ObservableCollection that gets filled with Books:
/// <summary>
///
/// </summary>
public bool FillBooksCtrlList()
{
List<Book> list = DBHelper.GetRecipes();
if (list == null)
{
return false;
}
else
{
BookList = new ObservableCollection<Book>(list);
foreach (Book book in BookList)
{
BookCtrlList.Add(new BookCtrl { DataContext = book, Margin = new Thickness(0, 10, 10, 0) });
}
return true;
}
}
In short, I am creating an instance of BookCtrl inside BookPanelCtrl
that belongs to the MainView.
Maybe the way I bind is the problem?
I think you want something like this. In your ViewModel you have an ObservableCollection containing Books:
public class SomeViewModel {
private ObservableCOllection<Book> _books;
public ObservableCollection<Book> Books
{
get
{
return _books;
}
set
{
_books = value;
RaisePropertyChanged("Books");
}
}
}
public bool FillBooksCtrlList()
{
List<Book> list = DBHelper.GetRecipes();
if (list == null)
{
return false;
}
else
{
Books = new ObservableCollection<Book>(list);
return true;
}
}
Then, in your View you could have something like:
<UserControl DataContext="SomeViewModel">
<ListBox ItemsSource="{Binding Books}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
This way the UserControl is bound to the ViewModel, the ListBox is bound to your list of Books and each item in the ListBox is bound to the Title property of a book (I'm guessing books have a title).
Hope this helps.
Related
This example is a simplified version of my requirement. If i can solve this then the rest is simple (I hope). Assume I have a class hierarchy of
Class A:
Name
List <Class B>
Class B:
Name
List <Class C>
Class C:
Name
Each class has a separate view, view model and model. My main view places each view in a DockPanel. The view of each class is a DataGrid that simply places the names of each list. When i click on a Name of class A it shows me names of class B below. and when i click a Name of class B it shows names of class C. So basically 3 user controls that are all DataGrid controls being shown on a dockpanel in my MainView.
I am still finding my feet in MVVM and I am not sure if this is the best way to go about doing this. The way i'd go about it is that each view model will have a property called SelectedItem being bound to the SelectedItem in the DataGrid on its view. When the property is set, another property called ChildViewModel will be set. But i am not sure how to set this up in my MainView and also in each control. How do i bind the control to it's parent datacontext and how do i bind the item source to the selected item from the parent?
I would have one ViewModel that is the DataContext for a Window/UserControl/etc that these controls are all grouped in. Then set up the collection of ClassA plus 3 SelectedItem properties. In the SelectedItem properties, you will want to set the next one to null or a default value.
private ObservableCollection<ClassA> _CollectionOfA;
public ObservableCollection<ClassA> CollectionOfA
{
get { return _CollectionOfA; }
set
{
if (value == _CollectionOfA)
return;
_CollectionOfA = value;
RaisePropertyChanged(() => CollectionOfA);
}
}
private ClassA _SelectedClassA;
public ClassA SelectedClassA
{
get { return _SelectedClassA; }
set
{
if (value == _SelectedClassA)
return;
_SelectedClassA = value;
SelectedClassB = null; //Or default this to first item in the list, etc.
RaisePropertyChanged(() => SelectedClassA);
}
}
//Repeat SelectedClassA pattern for SelectedClassB/SelectedClassC
public ClassB SelectedClassB { get; set; }
public ClassC SelectedClassC { get; set; }
Then in your bindings, you'll use SelectedClassA.CollectionOfClassB property as the ItemsSource for the 2nd DataGrid, etc.
<DataGrid ItemsSource="{Binding CollectionOfClassA}" SelectedItem="{Binding SelectedClassA}">
//columns defined here
</DataGrid>
<DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
//columns defined here
</DataGrid>
<DataGrid ItemsSource="{Binding SelectedClassB.CollectionOfClassC}" SelectedItem="{Binding SelectedClassC}">
//columns defined here
</DataGrid>
EDIT:
The first option is my KISS approach. Another option would be to use something like MVVM Light which implements a weak event pattern available through it's messenger class. In the constructor of your second ViewModel, you register to listen for changes to the SelectedItem in the first ViewModel. And in the setter of the first ViewModel you send a message that the SelectedItem changed. Here is an example of what ClassBViewModel would look like (ClassAViewModel would only need its SelectedClassA setter to send a message, no listener in the constructor. While ClassCViewModel would listen but not need to send).
public class ClassBViewModel : ViewModelBase
{
private ClassA _SelectedClassA;
public ClassA SelectedClassA
{
get { return _SelectedClassA; }
set
{
if (value == _SelectedClassA)
return;
_SelectedClassA = value;
RaisePropertyChanged(() => SelectedClassA);
}
}
private ClassB _SelectedClassB;
public ClassB SelectedClassB
{
get { return _SelectedClassB; }
set
{
if (value == _SelectedClassB)
return;
var oldValue = _SelectedClassB;
_SelectedClassB = value;
Messenger.Default.Send(new PropertyChangedMessage<ClassB>(oldValue, value, "SelectedClassB"));
RaisePropertyChanged(() => SelectedClassB);
}
}
public ClassBViewModel()
{
Messenger.Default.Register<PropertyChangedMessage<ClassA>>(this, (message) => SelectedClassA = message.NewValue);
}
}
Your ClassBView xaml for the DataGrid would still look the same:
<DataGrid ItemsSource="{Binding SelectedClassA.CollectionOfClassB}" SelectedItem="{Binding SelectedClassB}">
//columns defined here
</DataGrid>
I have a window with a combobox. This comboboxhas 5 ComboboxItems.
In the example I want that it is not possible to select the items 3, 4 and 5.
I've tried two different ways: MVVM way and codebehind way
MVVM way:
xaml:
<ComboBox SelectedIndex="{Binding Path=SaveIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SaveSelectedItemCheck}" Name="SaveCombobox">
viewmodel:
public object SaveSelectedItemCheck
{
get { return _control.SaveCombobox.Items[CurrentSaveIndex]; }
set
{
if (value != _control.SaveCombobox.Items[0] && value != _control.SaveCombobox.Items[1])
{
OnPropertyChanged("SaveSelectedItemCheck");
}
}
}
codebehind way:
xaml:
<ComboBox SelectedIndex="{Binding Path=SaveIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="Save_SelectionChanged">
codebehind:
private void Save_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox combobox = sender as ComboBox;
if(combobox == null)
{
return;
}
if (combobox.SelectedItem != combobox.Items[0] && combobox.SelectedItem != combobox.Items[1])
{
combobox.SelectedItem = combobox.Items[1];
e.Handled = true;
}
}
But it only works with the codebehind way, which is dirty.
Why doesn't work the MVVM way?
As others said, you do not actually set any value in the property setter.
But more important IMO, I think you've misunderstood the MVVM key concepts. There are lots of issues with your ViewModel code:
public object SaveSelectedItemCheck
{
get { return _control.SaveCombobox.Items[CurrentSaveIndex]; }
set
{
if (value != _control.SaveCombobox.Items[0] && value != _control.SaveCombobox.Items[1])
{
OnPropertyChanged("SaveSelectedItemCheck");
}
}
}
You're referring to _control.SaveCombobox.Items, which are UI concept/objects. This isn't the goal of the ViewModel. And you're returning an object, you should strongly type your model!
What you should have is the following:
a model (strongly typed POCO classes)
ViewModels that do not deal with the view controls in any way (you could even separate views and ViewModels into different assemblies to ensure you're following this rule)
Views, with binding to ItemsSource for control such as Combobox
Model:
public class SomeObject : INotifyPropertyChanged
{
private string someProperty;
public string SomeProperty
{
get { return this.someProperty; }
set
{
if (this.someProperty != value)
{
this.someProperty = value;
OnPropertyChanged("SomeProperty");
}
}
}
...
}
ViewModel:
public class ViewModel : SomeViewModelBase
{
private ObservableCollection<SomeObject> items;
private SomeObject selectedItem;
public ObservableCollection<SomeObject> Items
{
get
{
return items;
}
set
{
if (this.items != value)
{
this.items = value;
OnPropertyChanged("Items");
}
}
}
public ObservableCollection<SomeObject> SelectedItem
{
get
{
return selectedItem;
}
set
{
if (this.selectedItem != value)
{
this.selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
...
// Anywhere in your view model:
this.Items = new ObservableCollection<SomeObject>(...);
this.SelectedItem = this.Items[2];
// Etc.
}
View:
<ComboBox
ItemsSource={Binding Items}
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
View code-behind:
Nothing for your example
Your ViewModel method doesn't set the value of the property - regardless of whether the value is valid or not. It just fires an event based on whether the value is valid.
In fact, on closer inspection you appear to have misunderstood the MVVM pattern somewhat, as it appears that your ViewModel code might be referring directly to the control it is supporting. You should have a backing field for your property as per a "normal" property.
More importantly, you should throw the PropertyChanged event whether the value is valid or not, because if the value has been overriden by the viewmodel then PropertyChanged will notify the UI that the combobox value needs to be re-set to a valid value.
You don't store any value in the setter in your MVVM way.
I have a List box that I want to display a list of objects, I am following the MVVM pattern and am finding it difficult to achieve what I want.
MainWindowView.xaml
<ListBox ItemsSource="{Binding Path=MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainWindowViewModel.cs
private List<ListBoxItem> _myList = new List<ListBoxItem>();
public List<ListBoxItem> MyList
{
get { return _myList ; }
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
public SprintBacklogViewModel()
{
foreach(MyObject obj in MyObjects.MyObjectList)
{
ListBoxItem item = new ListBoxItem();
item.Content = obj;
MyList.Add(item);
}
}
MyList is getting updated correctly, but nothing is displaying in the window. (ItemsSource="{Binding Path=MyList}" also works, I tested with different data) I have not used an ItemTemplate before so any pointers are welcome. My understanding of it is that If I set it up correctly It will display the data in my objects.
eg:
<Label Content="{Binding Path=Name}"/>
there is a property in MyObject called Name, I want to display this as a label in my list
*EDIT
In my window I get a line of text - mynamespace.MyObject
MyList property in ViewModel is property of type ListBoxItem, it has property Name but it's not Name of MyObject. So you need to change your property in your ViewModel by
Replace
private List<ListBoxItem> _myList = new List<ListBoxItem>();
public List<ListBoxItem> MyList
{
get { return _myList ; }
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
with
private List<MyObject> _myList = new List<MyObject>();
public List<MyObject> MyList
{
get { return _myList ; }
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
Your list should not contain UI-Elements but data (you are data-binding), if you bind to a list of ListBoxItems the ListBox will disregard the ItemTemplate and just use the items as they fit the expected container for the ListBox. Containers will be generated automatically, you do not need to do that in your list.
If you add items to a collection at runtime the binding engine needs to be notified to update the changes, for this you should use an ObservableCollection or anything that implements INotifyCollectionChanged. (When doing so you further usually make the field readonly and only provide a getter) This is the reason why there are no items.
Tried may approches to displaying a "no data" if there are no items in listbox. Since I'm on wp7 and using silverlight I can't use DataTriggers, so I've created a control to have it behave consistently across the whole app. BUT I if you set the breakpoint for the set method - it's not being called at all!
The control class
public class EmptyListBox : ListBox
{
public new IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
// never here
base.ItemsSource = value;
ItemsSourceChanged();
}
}
protected virtual void ItemsSourceChanged()
{
bool noItems = Items.Count == 0;
if (noItems)
{
if (Parent is System.Windows.Controls.Panel)
{
var p = Parent as Panel;
TextBlock noData = new TextBlock();
noData.Text = "No data";
noData.HorizontalAlignment = HorizontalAlignment;
noData.Width = Width;
noData.Height = Height;
noData.Margin = Margin;
p.Children.Add(noData);
Visibility = System.Windows.Visibility.Collapsed;
}
}
}
}
This is xaml
<my:EmptyListBox ItemsSource="{Binding Path=MyData}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Codebehind:
ClientModel ClientInfo { get; set; }
public ClientView()
{
ClientInfo = new ClientModel();
ClientInfo.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(DataReady);
DataContext = ClientInfo
}
ClientModel class:
public class ClientModel : INotifyPropertyChanged
{
MyData _myData;
public MyData MyData
{
get
{
return _myData;
}
set
{
_myData = value;
NotifyPropertyChanged("MyData");
}
}
public void GetClient(int id)
{
// fetch the network for data
}
}
LINK TO SOLUTION .ZIP THAT SHOWS THE PROBLEM
http://rapidshare.com/files/455900509/WindowsPhoneDataBoundApplication1.zip
Your new ItemSource should be a DependencyProperty.
Anything that is working with Bindings have to be a DependencyProperty.
Simply make it a DependencyProperty.
I think the solution I'd go for is something like this:
Define a new visual state group ItemsStates and two visual states: NoItems and HasItems.
In the ControlTemplate for your custom listbox, add the visual tree for your "no data" state.
In the NoItems state, set the Visibility of your "no data" elements to Visible and set the Visibility of the default ItemsPresenter to Collapsed.
In the HasItems state, swap the Visibility of these elements.
In an OnApplyTemplate override switch to the Empty state by default: VisualStateManager.GoToState(this, "Empty", true);
In an OnItemsChanged override, check whether the items source is empty and use VisualStateManager to switch between these states accordingly.
That should work :)
Create ItemsSource as a DependencyProperty.
Example:
public IEnumerable ItemsSource
{
get { return (IEnumerable)base.GetValue(ItemsSourceProperty); }
set { base.SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(IEnumerable),
typeof(EmptyListBox),
new PropertyMetadata(null));
try to implement the INotifyPropertyChanged interface and use for ItemsSource an ObservableCollection. In the Setter of your Property just call the OnPropertyChanged method.
Maybe this will help.
Try adding Mode=TwoWay to the ItemsSource binding:
<my:EmptyListBox ItemsSource="{Binding Path=MyData, Mode=TwoWay}" Name="myListBox">
<my:EmptyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=name}" />
</DataTemplate>
</my:EmptyListBox.ItemTemplate>
</my:EmptyListBox>
Problem: The data I'm trying to display is essentially a collection of collections of some object. So the rows can be any number, normal for a datagrid, and the columns are also any number. That's not so normal for a datagrid. Usually you have a set number of columns and your rows vary. The datagrid cell will be either a string or a value changeable via a combobox.
Attempted Solution: I tried adding columns to the datagrid dynamically, and while this worked just fine (adding them in codebehind) the real issue I hit was how to bind to the underlaying objects. The data is being built dynamically and I tried a couple of formats. I tried a collection of Arrays, and also an ObservableCollection of ObservableCollections. I could bind to the objects but as Bindings in Silverlight have to bind to properties I couldn't come up with a solution presenting the data this way.
My solution as a result has been to display the data in a more traditional manner, with a list and a datagrid. When you select an item in the list it repopulates the data in the datagrid to show the objects.
Question: is there a way to bind a datagrid cell to a collection of collections of objects?
I found this question (WPF) which looks similar and it didn't help any. I think it's the same issue.
WPF DataGrid: DataGridComboxBox ItemsSource Binding to a Collection of Collections
One possible solution is instead of using a simple collection as the inner object, create a class derived from a collection and implement ICustomTypeDescriptor on it. In the interface implementation, iterate over the elements of the collection and populate your property descriptor collection accordingly. Once you do that, you should be able to bind to those properties from XAML.
Example - a data object based on a dictionary, which you can bind against its key names (I compressed to a single line all trivial method implementations):
class DictionaryDataObject : Dictionary<string, object>, ICustomTypeDescriptor
{
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes() { return AttributeCollection.Empty; }
public string GetClassName() { return "DictionaryDataObject"; }
public string GetComponentName() { return null; }
public TypeConverter GetConverter() { return null; }
public EventDescriptor GetDefaultEvent() { return null; }
public PropertyDescriptor GetDefaultProperty() { return null; }
public object GetEditor(Type editorBaseType) { return null; }
public EventDescriptorCollection GetEvents(Attribute[] attributes) { return EventDescriptorCollection.Empty; }
public EventDescriptorCollection GetEvents() { return EventDescriptorCollection.Empty; }
public PropertyDescriptorCollection GetProperties() { return GetProperties(null); }
public object GetPropertyOwner(PropertyDescriptor pd) { return this; }
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var pds =
this.Keys
.Select(x => new DictionaryPropertyDescriptor(x))
.ToArray();
return new PropertyDescriptorCollection(pds);
}
#endregion
}
class DictionaryPropertyDescriptor : PropertyDescriptor
{
public DictionaryPropertyDescriptor(string name) : base(name, null) { }
public override bool CanResetValue(object component) { return false; }
public override Type ComponentType { get { return null; } }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof(object); } }
public override void ResetValue(object component) { }
public override bool ShouldSerializeValue(object component) { return false; }
public override object GetValue(object component)
{
var dic = component as DictionaryDataObject;
if (dic == null) return null;
return dic[Name];
}
public override void SetValue(object component, object value)
{
var dic = component as DictionaryDataObject;
if (dic == null) return;
dic[Name] = value;
}
}
Sample object setup from code behind:
DictionaryDataObject ddo = new DictionaryDataObject();
public Window4()
{
ddo["propa"] = 1;
ddo["propb"] = "foo";
ddo["propc"] = "bar";
ddo["propd"] = 4.5;
InitializeComponent();
DataContext = ddo;
}
XAML usage:
<Window.Resources>
<DataTemplate x:Key="template">
<WrapPanel>
<TextBlock Text="{Binding propa}" Margin="5"/>
<TextBlock Text="{Binding propb}" Margin="5"/>
<TextBlock Text="{Binding propc}" Margin="5"/>
<TextBlock Text="{Binding propd}" Margin="5"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource template}"/>
</Grid>
If you want to adapt this solution to a list instead of a dictionary, you must set each property name based on some property of the list element.
I think I understand what you are trying to accomplish and I actually have a more elegant solution to your problem and it does not involve writing any custom classes. I wrote a blog post on this issue. The blog is geared towards the DataGrid from the Silverlight Toolkit, but you can easily modify it to use any grid.
The solution is here.
Let me know if this is what you were looking for.