My wpf project follows the MVVM pattern. In my view-model I have an IList of obejcts which I take from database. Every object from this IList has a property which is List.
When I open the view for that view-model, I have an ItemsControl with this property:
ItemsSource="{Binding TheIListOfObjects}"
and the items in that ItemsControl are actually showing information from List.
So, while the user is on the view, an itemscontrol is shown. What I want to do is this: while on the same view, if the user clicks a button, the List is changed. How can I make the ItemsControl to refresh and show the new info?
All you need to have is ObservableCollection type for the property type.
Xaml
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Content="Click" Click="Button_Click" />
<ListView ItemsSource="{Binding People}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return _people; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
People.Add(new Person { Name = "A" });
}
}
public class Person
{
public string Name { get; set; }
}
To make sure that changes in a collection are notified to its bound controls, you must use ObservableCollection<> instead of IList<>
In WPF, once we have data bound a collection to the ItemsSource property of a collection control, we don't refresh the ItemsSource property, or interact with it in any other way. Instead, we work with the data bound property value, so for your example...:
ItemsSource="{Binding TheIListOfObjects}"
... you should manipulate the TheIListOfObjects collection:
TheIListOfObjects = GetNewCollectionItems();
If you have correctly implemented the INotifyPropertyChanged interface in your view model, then your view should update as expected.
Related
I did my research that people tend to use ViewModel to achieve this but I am sort of stuck in it.
I have a
public ObservableCollection<Order> orderList { get; set; } = new ObservableCollection<Order>();
in MainWindow which is already filled up with data.
in MainWindow XAML I have a User Control inside the TabControl:
<TabControl x:Name="TabCollection">
<TabItem Header="UC1">
<local:UserControl1/>
</TabItem>
<TabItem Header="UC2">
<local:UserControl2/>
</TabItem>
</TabControl>
We only talk about UC1 here so in UC1 XAML here I have a ListView inside:
<UserControl.DataContext>
<local:UserControl1VM/>
</UserControl.DataContext>
<ListView x:Name="ListViewText">
<ListView.View>
<GridView>
<GridViewColumn Header="First name" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Lastname}"/>
<GridViewColumn Header="Order" DisplayMemberBinding="{Binding Ordername}"/>
<GridViewColumn Header="Delivery time" DisplayMemberBinding="{Binding Deliverytime}"/>
<GridViewColumn Header="Phone Number" DisplayMemberBinding="{Binding Phone}"/>
<GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}"/>
<GridViewColumn Header="Email" DisplayMemberBinding="{Binding Email}"/>
</GridView>
</ListView.View>
</ListView>
And here's the code in UserControl1VM.cs:
namespace QuickShop
{
class UserControl1VM : INotifyPropertyChanged
{
private ObservableCollection<Order> orderList;
public ObservableCollection<Order> OrderList
{
get { return orderList; }
set
{
orderList = value;
PropertyChanged(this, new PropertyChangedEventArgs("OrderList"));
}
}
//
private void FindDeliveryOrders(IEnumerable<Order> sortList)
{
foreach (var order in sortList)
{
if (order.Delivery.Equals("Yes"))
{
//deliveryOrders.Add(order);
this.ListViewText.Items.Add(new Order { Firstname = order.Firstname, Lastname = order.Lastname, Ordername = order.Ordername, Deliverytime = order.Deliverytime, Phone = order.Phone, Address = order.Address, Email = order.Email });
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
And Of course these are incomplete codes because I don't know how to proceed next.
My goal is just to populate the ListView and it will automatically update itself if orderList changes. But right now I couldn't even know whether the ViewModel is working or not, any thoughts and code demo would be very grateful.
A UserControl should never have a "private" view model, as you assign it to the DataContext in the UserControl's XAML. It should instead expose dependency properties that could be bound to properties of an externally provided view model object.
Declare an ItemsSource property like this:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource), typeof(IEnumerable), typeof(UserControl1));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public UserControl1()
{
InitializeComponent();
}
}
And bind the ListView like this:
<UserControl ...>
...
<ListView ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}">
...
</ListView>
...
</UserControl>
When you use the UserControl, bind the property to a view model property:
<TabItem Header="UC1">
<local:UserControl1 ItemsSource="{Binding OrderList}"/>
</TabItem>
The last XAML snippet assumes that the object in the UserControl's DataContext has a OrderList property. This would automatically happen when the TabControl is bound to a collection of view model objects with that property.
Alternatively, let the elements in the UserControl's XAML directly bind to the properties of the object in the inherited DataContext.
<UserControl ...>
...
<ListView ItemsSource="{Binding OrderList}">
...
</ListView>
...
</UserControl>
Your control would not have to expose additional bindable properties, but it would only work with DataContext objects that actually provide the expected source properties.
I have a Listview and i want to bind it to a list declared on the same class(codebehind)
public ObservableCollection<Slot> ListViewList { get; set; }
<ListView x:Name="ListViewSlots" Margin="0,230,0,0" ItemsPanel="{DynamicResource ItemsPanelTemplate1}" ItemsSource="{Binding Path=UserControl.ListViewList}" >
But is not working, i tried setting the datacontext of the usercontrol to self and desnt works.
Have you tried setting the DataContext of the UserControl to the list, and then setting the ItemsSource of the ListView to that?
ie.
<ListView ItemsSource="{Binding}" >
Add to your Window
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
and then your ListView becomes
<Listview ItemsSource="{Binding ListViewList}">...
first you have to introduce your list to the resources of the class:
public List<string> ListViewList
{
get{ return (List<string> Resources["ListViewList"];}
set{ Resources["ListViewList"] = value;}
}
or use ObservableCollection:
private ObservableCollection<string> _listViewList = new ObservableCollection<string>();
public ObservableCollection<string> ListViewList { get { return _listViewList; } }
then in XAML, you can bind something to it:
<ListView>
<ItemsPanel
ItemsPanel="{DynamicResource ItemsPanelTemplate1}"
ItemsSource="{Binding ListViewList}"
/>
</ListView>
and as Joel said you need to set the DataContext of the entire window
(or just the block you're dealing with) to self:
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
We're using Caliburn.Micro/Silverlight 4 and life is good.
I'm trying to bind a combobox's itemsSource to a viewModel, but this doesn't seem possible since the combobox is already bound to its own row's dataItem. The logic which fills the combo changes with other data on the screen so I can't really use a static list like I've been using.
Is there a way to bind directory to the viewModel somehow??? I've tried element to element binding but this never appears to work within the grid.
<Controls:DataGridTemplateColumn x:Name="FooNameCol" Header="Foo" MinWidth="200">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Foo.ShortName}"
Style="{StaticResource DataGridTextColumnStyle}"/>
</StackPanel>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
MinWidth="200" MinHeight="25"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding Officers, Source={StaticResource ReferenceListRetriever}}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
Within a DataTemplate, the DataContext is bound to each single item of the corresponding list; since all Bindings implicitly refers to DataContext, you have to ensure that the path is valid, starting from the single data item.
In your scenario, for the indicated binding to work, you should have a VM shaped this way:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
public IEnumerable<Officer> Officers {get;}
}
It may seem an overkill, but in some scenarios each combo can actually contain different choices for each data item, based on some business rule.
In simpler cases MyItem can just expose a common list coming from the parent MyVM:
public class MyItem {
...
public IEnumerable<Officer> Officers {
get { return _parent.AvailableOfficers; }
}
}
If you really can't live with it and prefer to keep the available Officers list in the root VM only, you can use a Xaml side trick:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
public IEnumerable<Officer> Officers {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
}
Xaml:
<UserControl ...>
...
<AnyFrameworkElementAtThisLevel Name="bridge" />
...
<Controls:WhateverGrid>
...
<Controls:DataGridTemplateColumn ...>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
...
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding DataContext.Officers, ElementName=bridge}" />
</DataTemplate>
I have code like this:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>Some Other Stuff Here</TextBlock>
<ComboBox ItemsSource="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is, every time the outside ListBox.SelectedItem gets changed, the ComboBoxes inside it would change their SelectedIndex to -1. Which means if I click "Some Other Stuff Here" (unless the ListBoxItem it is in is selected), all the comboboxes' selection get cleared.
How do I overcome this? Thx!
Presumably your combobox is bound to something like an ObservableCollection - try exposing an instance of ICollectionView instead:
class DataSource
{
// ...
public ObservableCollection<string> MyData { get; private set; }
public ICollectionView MyDataView
{
get
{
return CollectionViewSource.GetDefaultView(this.MyData);
}
}
}
You can then bind your combobox with:
<ComboBox ItemsSource="{Binding MyDataView}" IsSynchronizedWithCurrentItem="True" />
This means that the 'selected item' for each data source is stored in the ICollectionView object instead of within the combobox, which should mean that it is persisted when the ListBox SelectedItem changes
Is it possible to bind an Event in a Silverlight DataTemplate? If so, what is the best way to do it?
For example, say you've created a DataTemplate that has a Button in it, like this:
<UserControl.Resources>
<DataTemplate x:Key="MyDataTemplate" >
<Grid>
<Button Content="{Binding ButtonText}" Margin="4" />
</Grid>
</DataTemplate>
</UserControl.Resources>
Then, you apply it to a ListBox ItemTemplate, like this:
<Grid x:Name="LayoutRoot" Background="White">
<ListBox x:Name="lbListBox" ItemTemplate="{StaticResource MyDataTemplate}" />
</Grid>
If you set the ListBox's ItemSource to a list of objects of the class:
public class MyDataClass
{
public string ButtonText{ get; set; }
}
How then do you catch the button click from each button from the DataTemplate in the list? Can you use binding to bind the Click event to a method in "MyButtonClass", like this:
<UserControl.Resources>
<DataTemplate x:Key="MyDataTemplate" >
<Grid>
<Button Click="{Binding OnItemButtonClick}" Content="{Binding ButtonText}" Margin="4" />
</Grid>
</DataTemplate>
</UserControl.Resources>
Would this work? If so, what should I put in the "MyDataClass" to catch the event?
Thanks,
Jeff
There are a couple of options.
One. make a custom control that is bound the data object for that row. On that custom control add the handler for the bound object.
I dont think your binding on the click will work. Sans the Binding Statment and just declare your click to a string.
Add the handler on the page where the control is housed.
Keep in mind that if you bind this way you will only be able to work with the sender of that item (button) and it's properties. If you need to get at specific attributes on an object you maybe better off pursuing the first option.
Small Example demonstrating the functionality by adding 10 buttons to a list box with click events. HTH
DataTemplate XAML
<UserControl.Resources>
<DataTemplate x:Name="MyDataTemplate">
<Grid>
<Button Click="Button_Click" Content="{Binding ItemText}"/>
</Grid>
</DataTemplate>
</UserControl.Resources>
ListBox XAML
<ListBox x:Name="ListBoxThingee" ItemTemplate="{StaticResource MyDataTemplate}"/>
Code Behind (I just plugged this all into the page.xaml file
public class MyClass
{
public string ItemText { get; set; }
}
public partial class Page : UserControl
{
ObservableCollection<MyClass> _Items;
public Page()
{
InitializeComponent();
_Items = new ObservableCollection<MyClass>();
for (int i = 0; i < 10; i++)
{
_Items.Add(new MyClass() {ItemText= string.Format("Item - {0}", i)});
}
this.ListBoxThingee.ItemsSource = _Items;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Button _b = sender as Button;
if (_b != null)
{
string _s = _b.Content as string;
MessageBox.Show(_s);
}
}
}
What I would do is create a button that uses the command pattern for click handling. In the .NET 4 framework you can bind commands to those that exist on the view model.