I am displaying the data in the gridview in a grouped style. I am already can create new items. Now I want to create a function that can delete the item that I create. Here is my viewmodel :
Viewmodel
public class VM : INotifyPropertyChanged
{
public VM()
{
DeleteItem = new DelegateCommand(DeleteCurrentItem);
}
public ObservableCollection<Contact> ContList = new ObservableCollection<Contact>();
private ObservableCollection<Category> _GroupedCollection;
public ObservableCollection<Category> GroupedCollection
{
get
{
if (_GroupedCollection == null)
_GroupedCollection = new ObservableCollection<Category>();
return _GroupedCollection;
}
set
{
_GroupedCollection = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("GroupedCollection"));
}
}
public void DeleteCurrentItem(object param)
{
var cont= param as Contact;
// there is another class that declare another ObservableCollection that holds all the models.
var category = GroupedCollection.FirstOrDefault(g => g.Key == cont.Account);
if (category != null)
{
if (category.CredCategory.Contains(cont))
{
category.CredCategory.Remove(cont);
}
}
}
public DelegateCommand DeleteItem { get; set; }
private string _Account;
public string Account
{
get { return _Account; }
set
{
_Account = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Account"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In my XAML, I have a flyout, which work as desired. I can hold the data displayed and the flyout will appear/open. But when I click "Delete", the 'gridview' does not delete it.
View (XAML)
<Page.DataContext>
<data:VM/>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Key="cvs" IsSourceGrouped="True"
Source="{Binding GroupedCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsPath="CredCategory"/>
</Page.Resources>
<Grid>
<FlyoutBase.AttachedFlyout>
<MenuFlyout x:Name="flyout">
<MenuFlyoutItem Text="Delete"
Command="{Binding DataContext.DeleteItem, ElementName=gridview}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<GridView x:Name="gridview"
ItemsSource="{Binding Source={StaticResource cvs}}"
<GridView.ItemTemplate>
<DataTemplate>
. . . .
<DataTemplate/>
<GridView.ItemTemplate>
<GridView/>
<Grid/>
I am showing the code-behind in case someone wants to see it.
View (Code-Behind)
public void cardstack_pass_Holding(object sender, HoldingRoutedEventArgs e)
{
//this is the event declared in the Datatemplate inside gridview
flyout.ShowAt(sender as FrameworkElement);
e.Handled = true;
}
As I stated at the above, my problem is when I click the "Delete" on flyout, it should be deleting the data from the ObservableCollection right? Because as far as I know, the DataContext of the flyout is the DataContext of the data displayed, or am I wrong? How to fix this?
I mean, the gridview's DataContext is the ObservableCollection, and the Stackpanels' DataContext inside gridview's DataTemplate will be the Model Contact right? Since flyout was open at the item created, so the DataContext of flyout will inherit from the item's DataContext, and if the flyout's CommandParameter = "{Binding}", it should pass the Contact inside the item to the viewmodel, isn't it?
I might be missing something here but shouldn't the AttachedFlyout code go in the DataTemplate
Note Binding of Command to element name root (Page name) as we're inside the GridView eg
<Page x:Name="root">
<Page.DataContext>
<data:VM/>
</Page.DataContext>
<Page.Resources>
<CollectionViewSource x:Key="cvs" IsSourceGrouped="True"
Source="{Binding GroupedCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsPath="CredCategory"/>
</Page.Resources>
<Grid>
<GridView x:Name="gridview"
ItemsSource="{Binding Source={StaticResource cvs}}"
<GridView.ItemTemplate>
<DataTemplate>
<FlyoutBase.AttachedFlyout>
<MenuFlyout x:Name="flyout">
<MenuFlyoutItem Text="Delete"
Command="{Binding DataContext.DeleteItem, ElementName=root}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<DataTemplate/>
<GridView.ItemTemplate>
<GridView/>
<Grid/>
This article shows how to use Behaviours which are available in UWP.
Related
I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"
I have a user control that I am using to populate a datagrid.
I would like the user to be able to add items by editing the empty row at the bottom. (This is why I am using a datagrid rather than an itemscontrol) However the datagrid does not realise that the last item is edited unless the user clicks the background of the control. I would like the new item to be added when the user makes changes on the properties that the control exposes.
XAML of the control:
<UserControl x:Class="ControlTest.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ControlTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300"
DataContext="{Binding Path=., Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=p1, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="300"
Height="30"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding Path=DropDownValues,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:MyControl}}"
SelectedItem="{Binding Path=p2, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Height="30"/>
</StackPanel>
</UserControl>
cs:
public partial class MyControl : UserControl
{
private static readonly DependencyProperty DropDownValuesProperty =
DependencyProperty.Register(
"DropDownValues",
typeof(List<String>),
typeof(MyControl),
new FrameworkPropertyMetadata(new List<String>()
));
public List<String> DropDownValues
{
get
{
return (List<String>)GetValue(DropDownValuesProperty);
}
set
{
SetValue(DropDownValuesProperty, value);
}
}
public MyControl()
{
InitializeComponent();
}
}
DataGrid XAML
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding objs, Mode=TwoWay}"
HeadersVisibility="None"
Margin="0,0,0.4,0"
CanUserAddRows="True"
>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
<DataGrid.Columns>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Measure">
<local:MyControl
DataContext="{Binding ., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DropDownValues=
"{Binding DataContext.list, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Width="300"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Can I make this work, and/or is there a better way to do this?
I would like to suggest you a different way of doing that:
set CanUserAddRows=false on your DataGrid and then manually add rows to the ObservableCollection<Something> to which your DataGrid is bound to.
OR
If you are still interested in the approach that you follow:
In your xaml file:
<DataGrid x:Name="myDataGrid" CellEditEnding="DataGrid_CellEditEnding" .....>
<!--Some Code-->
</DataGrid>
Then in the Code-Behind:
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
myDataGrid.CommitEdit();
}
If you don't understand anything then feel free to ask.
Update
If you are following the same approach:
In your DataGrid's Beginning edit event you can try:
private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if ((selectedRow as DataGridRow).Item.ToString() != "{NewItemPlaceholder}")
{
//Here you can add the code to add new item. I don't know how but you should figure out a way
}
}
Note: The code mentioned above is not tested.
I would also suggest you :
Not to use DataGrid. Instead use ListBox. Because, you are trying to add some data. At this time you never need sorting, searching and column-reordering fascilities. In such scenario, ListBox is useful as it is light-weight control than datagrid. I have a sample here: https://drive.google.com/open?id=0B5WyqSALui0bTXFGZWxQUWVRdkU
Is the problem that the UI is not being notified of changes to the objs collection? What I would do is try setting up whatever view model that contains objs to make objs an observable collection. I would also ensure that whatever objs is an observable collection of implements INotifyPropertyChanged and that properties p1 and p2 both fire OnPorpertyChanged when they are set.
public ObservableCollection<YourObject> objs
and
public class YourObject : INotifyPropertyChanged
{
protected void OnPropertyChanged(string Name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(Name));
}
}
private string _p1;
public string p1
{
get { return _p1; }
set
{
if (_p1 != value)
{
_p1 = value;
OnPropertyChanged("p1");
}
}
}
private string _p2;
public string p2
{
get { return _p2; }
set
{
if (_p2 != value)
{
_p2 = value;
OnPropertyChanged("p2");
}
}
}
}
I have 2 viewmodels that inherit from the same BaseViewModel, which has an ObservableCollection as a public property.
The first viewmodel shows the ObservableCollection, while the second viewmodel allows for updating the collection.
How come the first view doesn't see the updated collection?
public class BaseViewModel : INotifyPropertyChanged
{
private Playlist _currentPlaylist;
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public Playlist CurrentPlaylist
{
get
{
if (_currentPlaylist == null)
{
_currentPlaylist = _library.CurrentPlaylist;
}
return _currentPlaylist;
}
set
{
_currentPlaylist = value;
NotifyPropertyChanged("CurrentPlaylist");
}
}
public BaseViewModel()
{
_library = new Library();
_dbContext = new MusicTrackerDataContext();
}
}
The first view that uses the inherited BaseViewModel uses CurrentPlaylist databound.
The second view sets the CurrentPlaylist again:
public class ArtistPageViewModel : BaseViewModel
{
public void PlaylistBtn_Clicked(ListView albumListView)
{
Library.AddSelectionToCurrentPlaylist(albumListView.SelectedItems.Cast<Album>());
CurrentPlaylist = Library.CurrentPlaylist;
}
}
Seeing that my BaseViewModel raises the INotifyPropertyChanged event when I set my CurrentPlaylist, I'd expect that the listview to which my CurrentPlaylist is bound, is updated.
What am I doing wrong?
Edit
The code for the View that's showing the old collection
public sealed partial class HubPage : Page
{
private HubPageViewModel _hubPageViewModel;
public HubPageViewModel HubPageViewModel
{
get
{
return _hubPageViewModel;
}
}
}
The XAML code for my HubPageViewModel
<Page
x:Class="MyProject.HubPage"
DataContext="{Binding HubPageViewModel, RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<HubSection x:Uid="HubSection5" Header="Now Playing"
DataContext="{Binding CurrentPlaylist}" HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<ListView
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding Tracks}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
</HubSection>
Edit 2
I've changed my code to the following (according to the comments)
public sealed partial class HubPage : Page
{
private readonly NavigationHelper navigationHelper;
private static HubPageViewModel _hubPageViewModel; // Made it static
public HubPageViewModel HubPageViewModel
{
get
{
return _hubPageViewModel;
}
}
}
<Page
x:Class="MyProject.HubPage"
DataContext="{Binding HubPageViewModel, RelativeSource={RelativeSource Self}}"
xmlns:vm="using:MyProject.ViewModels" // Added reference to my VM
mc:Ignorable="d">
<ResourceDictionary>
<vm:HubPageViewModel x:Name="hubPageViewModel"/> // Added the key
</ResourceDictionary>
<HubSection x:Uid="HubSection5" Header="Now Playing"
DataContext="{Binding Source={StaticResource hubPageViewModel}}" HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<ListView
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding CurrentPlaylist.Tracks}"
ItemClick="ItemView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,27.5" Holding="StackPanel_Holding">
<TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
But it's still not updated when I return from my second viewmodel.
Even though both view models inherit the same base class, the state of the base class is not shared between the two view models. That is, HubPageViewModel and ArtistPageViewModel do not share the same instance of Playlist. They are completely different properties.
Since both view models points to the same playlist instance and that instance is an ObservableCollection, all changes to that instance of ObservableCollection will be shared between the two view models because both views are bound to the "collection changed" notifications for the instance they are watching. In your example, ArtistPageViewModel.PlaylistBtn_Clicked does not changes the data within the ObservableCollection, it changes the collection itself. This changes the collection that the second view is watching but does not change the one of the first view.
I am new to MVVM and still trying to get a grasp on it so let me know if I'm setting this up wrong. What I have is a UserControl with a ListView in it. I populate this ListView with data from the ViewModel then add the control to my MainView. On my MainView I have a button that I want to use to add an item to the ListView. Here is what I have:
Model
public class Item
{
public string Name { get; set; }
public Item(string name)
{
Name = name;
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<Item> _itemCollection;
public ViewModel()
{
ItemCollection = new ObservableCollection<Item>()
{
new Item("One"),
new Item("Two"),
new Item("Three"),
new Item("Four"),
new Item("Five"),
new Item("Six"),
new Item("Seven")
};
}
public ObservableCollection<Item> ItemCollection
{
get
{
return _itemCollection;
}
set
{
_itemCollection = value;
OnPropertyChanged("ItemCollection");
}
}
}
View (XAML)
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<Grid>
<ListView ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding ItemCollection}">
</ListView>
</Grid>
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.mainContentControl.Content = new ListControl();
}
private void Button_Add(object sender, RoutedEventArgs e)
{
}
}
MainWindow (XAML)
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Width="100" Height="30" Content="Add" Click="Button_Add" />
</StackPanel>
<ContentControl x:Name="mainContentControl" />
</DockPanel>
</Grid>
Now, from what I understand, I should be able to just an item to ItemCollection and it will be updated in the view. How do I do this from the Button_Add event?
Again, if I'm doing this all wrong let me know and point me in the right direction. Thanks
You should not interact directly with the controls.
What you need to do is define a Command (a class that implements the ICommand-interface) and define this command on your ViewModel.
Then you bind the Button's command property to this property of the ViewModel. In the ViewModel you can then execute the command and add an item directly to your list (and thus the listview will get updated through the automatic databinding).
This link should provide more information:
http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx#sec11
I have MVVM master /details like this:
<Window.Resources>
<DataTemplate DataType="{x:Type model:EveryDay}">
<views:EveryDayView/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:EveryMonth}">
<views:EveryMonthView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="12,24,0,35" Name="schedules"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Elements}"
SelectedItem="{Binding Path=CurrentElement}"
DisplayMemberPath="Name" HorizontalAlignment="Left" Width="120"/>
<ContentControl Margin="168,86,32,35" Name="contentControl1"
Content="{Binding Path=CurrentElement.Schedule}" />
<ComboBox Height="23" Margin="188,24,51,0" Name="comboBox1"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Schedules}"
SelectedItem="{Binding Path=CurrentElement.Schedule}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValue="{Binding Path=CurrentElement.Schedule.ID}"/>
</Grid>
This Window has DataContext class:
public class MainViewModel : INotifyPropertyChanged {
public MainViewModel() {
elements.Add(new Element("first", new EveryDay("First EveryDay object")));
elements.Add(new Element("second", new EveryMonth("Every Month object")));
elements.Add(new Element("third", new EveryDay("Second EveryDay object")));
schedules.Add(new EveryDay());
schedules.Add(new EveryMonth());
}
private ObservableCollection<ScheduleBase> _schedules = new
ObservableCollection<ScheduleBase>();
public ObservableCollection<ScheduleBase> Schedules {
get {
return _schedules;
}
set {
schedules = value;
this.OnPropertyChanged("Schedules");
}
}
private Element _currentElement = null;
public Element CurrentElement {
get {
return this._currentElement;
}
set {
this._currentElement = value;
this.OnPropertyChanged("CurrentElement");
}
}
private ObservableCollection<Element> _elements = new
ObservableCollection<Element>();
public ObservableCollection<Element> Elements {
get {
return _elements;
}
set {
elements = value;
this.OnPropertyChanged("Elements");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
One of Views:
<UserControl x:Class="Views.EveryDayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid >
<GroupBox Header="Every Day Data" Name="groupBox1" VerticalAlignment="Top">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBox Name="textBox2" Text="{Binding Path=AnyDayData}" />
</Grid>
</GroupBox>
</Grid>
My SelectedItem in ComboBox doesn't works correctly. Are there any visible errors in my code?
What I usually do is bind the items of an ItemsControl to an ICollectionView (usually ListCollectionView) instead of directly to a collection; I think that's what the ItemsControl does by default anyway (creates a default ICollectionView), but I might be wrong.
Anyway, this allows you to work with the CurrentItem property of the ICollectionView, which is automatically synchronized with the selected item in an ItemsControl (if the IsSynchronizedWithCurrentItem property of the control is true or null/default). Then, when you need the current item in the ViewModel, you can use that instead. You can also set the selected item by using the MoveCurrentTo... methods on the ICollectionView.
But as I re-read the question I realize you may have another problem altogether; you have a collection of 'default' items and need a way to match them to specific instances. It would however be a bad idea to override the equality operators of the objects to consider them always equal if they are of the same type, since that has the potential to make other code very confusing. I would consider extracting the type information into an enum, and put a read-only property on each object returning one of the enum values. Then you can bind the items to a collection of the enum values, and the selected item to the enum property of each object.
Let me know if you need an example, I may have made a mess of the explanation :)