ArgumentException while reordering listViewItems - c#

I'm trying to reorder listView items with a mouse and I'm getting this mysterious Parameter is not valid ArgumentException either when I'm starting to drag or when I'm dropping item. There are no other details, no stack trace. Crashes entire app.
It works fine when I'm reordering ObservableCollection<string> but keeps crashing on ObservableCollection<MyControl>
MyControl is just a simple UserControl with a TextBlock inside.
I tried CollectionViewSource approach and it's the same.
Any ideas?
MainPage.xaml
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True"/>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MainPage : Page
{
private MainPageViewModel mainPageViewModel;
public MainPage()
{
this.InitializeComponent();
mainPageViewModel = new MainPageViewModel();
DataContext = mainPageViewModel;
// THIS IS NOT WORKING
MyControl myControl1 = new MyControl("Hello1");
MyControl myControl2 = new MyControl("Hello2");
MyControl myControl3 = new MyControl("Hello3");
mainPageViewModel.Items.Add(myControl1);
mainPageViewModel.Items.Add(myControl2);
mainPageViewModel.Items.Add(myControl3);
// THIS IS WORKING
//string s1 = "h1";
//string s2 = "h2";
//string s3 = "h3";
//mainPageViewModel.Items.Add(s1);
//mainPageViewModel.Items.Add(s2);
//mainPageViewModel.Items.Add(s3);
}
}
}
MainPageViewModel.cs
using System.Collections.ObjectModel;
namespace App3
{
public class MainPageViewModel : BaseModel
{
// THIS IS NOT WORKING
private ObservableCollection<MyControl> items = new ObservableCollection<MyControl>();
public ObservableCollection<MyControl> Items
{
get { return items; }
set
{
items = value;
OnPropertyChanged();
}
}
// THIS IS WORKING
//private ObservableCollection<string> items = new ObservableCollection<string>();
//public ObservableCollection<string> Items
//{
// get { return items; }
// set
// {
// items = value;
// OnPropertyChanged();
// }
//}
}
}
MyControl.xaml
<UserControl
x:Class="App3.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<TextBlock Name="tb"/>
</UserControl>
MyControl.cs
using Windows.UI.Xaml.Controls;
namespace App3
{
public sealed partial class MyControl : UserControl
{
public MyControl(string sName)
{
this.InitializeComponent();
tb.Text = sName;
}
}
}
BaseModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App3
{
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string name = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
EDIT
Looks like there's also no problem with reordering simple objects, so I guess there is something wrong with reordering UserControl.
namespace App3
{
public class SampleClass
{
private string sName;
public SampleClass(string sName)
{
this.sName = sName;
}
public override string ToString()
{
return sName;
}
}
}
EDIT 2
Reordering objects that derive from CheckBox crashes exactly like MyControl.
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : CheckBox
{
public MyCheckBox(string sName)
{
Content = sName;
}
}
}
Same for standard checkBoxes
CheckBox checkBox1 = new CheckBox() { Content = "hello1" };
CheckBox checkBox2 = new CheckBox() { Content = "hello2" };
CheckBox checkBox3 = new CheckBox() { Content = "hello3" };
mainPageViewModel.Items.Add(checkBox1);
mainPageViewModel.Items.Add(checkBox2);
mainPageViewModel.Items.Add(checkBox3);
EDIT 3
Seems like the only solution is to use simple class for holding data
using Windows.UI.Xaml.Controls;
namespace App3
{
public class MyCheckBox : BaseModel
{
private string sName;
public string Name
{
get { return sName; }
set
{
sName = value;
OnPropertyChanged();
}
}
public MyCheckBox(string sName)
{
Name = sName;
}
}
}
and DataTemplate for ListViewItem to display it
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ListView ItemsSource="{Binding Items}" CanReorderItems="True" AllowDrop="True">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
instead of adding UserControl directly to ListView items.

It looks like the reordering of Visuals is not supported entirely. When reorganizing items in a listview, a lot happens underneath, as for example dynamically creating and destroying containers that hold your content, etc.
In order to display the resorted element, some items need to be appended elsewhere in the visual tree.
The drag/drop tries to attach an already attached Visual (your UserControl) which might cause the exception - a Visual can not be attached to multiple parents.
Your approach of using POCOs / ViewModels with Datatemplates should be sufficient to get the work done, as the de- and attach semantics are considered by ListView already.
There should be more exception details, though.

Related

WPF - Force Binding on Invisible ComboBox

I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}
I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.

How to Bind ObervableCollection or IEnumerable to Flow Document Table

How Can I Bind an ObservableCollection or IEnumerable property in my ViewModel to a Table Defined by an Inline FlowDocument? I'm not sure whether it is the correct approach. I want to print an Invoice from my view model(I thought FlowDocument might be an easy method to print).
Suppose I have a Property Items in my ViewModel
private ObservableCollection<ItemViewModel> _Items;
public ObservableCollection<ItemViewModel> Items
{
get
{
return _Items;
}
set
{
_Items = value;
RaisePropertyChanged("Items");
}
}
In Xaml: MainWindow.xaml
....
<FlowDocument>
<Paragraph>
<Run Text="Sample text"/>
</Paragraph>
...
<Table <!-- I cannot find ItemsSource Or anything similar --> >
<!-- How can i dynamically generate rows and add them here from Items in ViewModel
Something like Datagrid's DataGridTextColumn ?
-->
</Table>
</FlowDocument>
FlowDocument is not a seperate file or an xps. It is embedded inside my MainWindow.xaml file
I'm a beginner and not sure how to do it.
Any help appreciated.
thank you
Had to get back to my history, yeah we experienced this before we found this article which says "there is no support for data binding in flow documents". So we decided to use itemscontrol instead and didn't use FlowDocument at all.
But here is a sample workaround,
MainWindow.xaml
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<FlowDocument>
<Paragraph>
<Run Text="Sample text"/>
</Paragraph>
<Table x:Name="Table1">
</Table>
</FlowDocument>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using WpfApp3.ViewModel;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainViewModel VM => (MainViewModel) DataContext;
public MainWindow()
{
InitializeComponent();
BuildTable();
}
private void BuildTable()
{
foreach (ItemViewModel item in VM.Items)
{
TableRow nameRow = BuildRow(item.Name);
TableRowGroup group = new TableRowGroup();
group.Rows.Add(nameRow);
Table1.RowGroups.Add(group);
}
}
private static TableRow BuildRow(string content)
{
TextBlock textBlock = new TextBlock
{
Text = content
};
Block block1 = new BlockUIContainer(textBlock);
TableCell cell = new TableCell();
cell.Blocks.Add(block1);
TableRow row = new TableRow();
row.Cells.Add(cell);
return row;
}
}
}
ViewModel->MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp3.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
PopulateData();
}
private ObservableCollection<ItemViewModel> _Items;
public ObservableCollection<ItemViewModel> Items
{
get => _Items;
set
{
_Items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region Stub
private void PopulateData()
{
Items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel
{
Name = "Item 1",
},
new ItemViewModel
{
Name = "Item 2",
}
};
}
#endregion
}
}
ViewModel->ItemViewModel.cs
namespace WpfApp3.ViewModel
{
public class ItemViewModel
{
public string Name { get; set; }
}
}
Output:

Asynchronous MVVM for WPF C# using MongoDB

So my situation is this: I want to be able to use MVVM with my WPF application using MongoDB. I am very new to MVVM (I know very little of it), but I've got some experience using .NET and WPF.
I have a namespace for recalling MongoDB collections, with the Model component stored there as a class called "User"
Model (in a separate namespace):
public class User
{
[BsonElement("_id")]
public ObjectId Id { get; set; }
public string name { get; set; }
// other methods listed here
public async static Task<List<User>> getUserList()
{
// allows me to get a list of users
var col = MongoDBServer<User>.openMongoDB("Users");
var filter = Builders<User>.Filter.Exists("name");
List<User> userList = await col.Find(filter).ToListAsync();
return userList;
}
}
I've created a very basic ViewModelBase (abstract ViewModelBase):
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if(handler == null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And a derived class for handling the User Lists (ViewModel):
public class UserListViewModel : ViewModelBase
{
private User _user;
private ObservableCollection<User> _userList;
public User user
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("user");
}
}
public ObservableCollection<User> userList
{
get { return _userList; }
set
{
_userList = value;
OnPropertyChanged("userList");
}
}
public UserListViewModel()
{
user = new User();
this.userList = new ObservableCollection<User>();
// since MongoDB operations are asyncrhonous, the async method "getUserList()" is used to fill the observable collection
getUserList().Wait();
}
public async Task getUserList()
{
var UserListRaw = await User.getUserList();
this.userList = new ObservableCollection<User>(UserListRaw);
}
}
The view component is as a simple window with a listbox, as follows (View):
<Window x:Class="UserManagementMVVM.UsersWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UserManagementMVVM"
mc:Ignorable="d"
Title="UsersWindow" Height="300" Width="300">
<Window.Resources>
<local:UserListViewModel x:Key="ViewModel"/>
<!-- Receiving error for this XAML block saying "Object reference not set to instance of an object -->
</Window.Resources>
<Grid DataContext="{Binding ViewModel}">
<ListBox Margin="5" ItemsSource="{Binding userList}"/>
</Grid>
</Window>
The App.Xaml and its codebehind are left untouched, as is the View's codebehind.
When I run the program, nothing shows up (ie: The Window starts, but the ListBox is empty even though there is data). I will soon add some button functionality that will perform atomic operations with MongoDB.
I've been trying for nearly 2 weeks to make my own MVVM program for this, with no success. Any assistance would be greatly appreciated.
You are not putting the getUserList() return value into a variable
I assume you mean to do the following
Task.Run(async ()=>this.userList = await getUserList());
this shall work you should think wether you want to wait for the task to finish or not, and than place a .Wait() after it.
Your other issue might be the way you bind to the ViewModel in the context it should use StaticResource instead of binding
like This:
<Grid DataContext="{StaticResource ViewModel}">
<Window x:Class="UserManagementMVVM.UsersWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UserManagementMVVM"
mc:Ignorable="d"
Title="UsersWindow" Height="300" Width="300">
<Window.DataContext>
<!--You have to set the DataContext -->
<local:UserListViewModel x:Key="ViewModel"/>
</Window.DataContext>
<Grid>
<ListBox Margin="5" ItemsSource="{Binding userList}"/>
</Grid>
</Window>
You have to set the DataContext right. i changed your xaml. but i prefer setting the DataContext for the Mainwindow in Codebehind or app.xaml.cs.
eg: app.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
var data = new MainWindowViewmodel();
this.MainWindow = new MainWindow(data);
this.MainWindow.Show();
}
all other DataContext for my views are done with DataTemplates within the ResourceDictionary
<DataTemplate DataType="{x:Type local:MyOtherViewmodel}">
<local::MyOtherViewmodelView />
</DataTemplate>
I want to give credit to both gilMishal and blindmeis for pointing me in the right direction. Both of your answers have helped. Here is my updated (and functional!) code:
App.xaml.cs has been modified as follows (Credit to blindmeis):
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
UsersWindow window = new UsersWindow();
var ViewModel = new UserListViewModel();
window.DataContext = ViewModel;
window.Show();
}
}
The ViewModel has been updated:
public class UserListViewModel : ViewModelBase
{
private User _user;
private ObservableCollection<string> _userList; // changed from "User" class to string
public User user
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("user");
}
}
public ObservableCollection<string> userList
{
get { return _userList; }
set
{
_userList = value;
OnPropertyChanged("userList");
}
}
public UserListViewModel()
{
userList = new ObservableCollection<string>();
Task.Run(async () => this.userList = await getUserList()); // Credit to gilMishal
}
public async Task<ObservableCollection<string>> getUserList()
{
var UserListRaw = await User.getUserList();
var userListOC = new ObservableCollection<string>();
foreach (var doc in UserListRaw) // extracting the "name" property from each "User" object
{
userListOC.Add(doc.name);
}
return userListOC;
}
}
And the view:
<Window x:Class="UserManagementMVVM.UsersWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UserManagementMVVM"
mc:Ignorable="d"
Title="UsersWindow" Height="300" Width="300">
<Window.Resources>
<local:UserListViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid> <!-- data context removed from here, credit blindmeis -->
<ListBox Margin="5" ItemsSource="{Binding userList}"/>
</Grid>
</Window>

How do you perform Binding with a DataGridView in WPF?

I want to bind a datagrid view in a user control that is docking to a main WPF form. However everytime I try to bind the data it must pre exist and won't update. Is there a way to perform this in the XAML directly to know when an event is triggered to update the datagridview rather than do it in the code behind?
Partial code of XAML:
xmlns:c="clr-namespace:TestWPFMain"
<UserControl.Resources>
<c:GridData x:Key="dataforGrid"/>
</UserControl.Resources>
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding Source={StaticResource dataforGrid}, Path=Results, Mode=TwoWay}" />
</Grid>
Code Behind for UserControl above:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
//datagridMain.ItemsSource = gd.Results;
-- This code above will work if I uncomment but I want it to be bound
directly and was curious as I thought the mode of 'two way' would
do this. I am not certain and most examples assume property is already
set up and not being created and updated.
}
Code Class for GridData:
class PersonName
{
public string Name { get; set; }
}
class GridData
{
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
var list = be.tePersons.Select(x => new PersonName { Name = x.FirstName });
Results = new ObservableCollection<PersonName>(list);
}
}
}
To use binding like this, you need to:
Set the DataContext correctly on the DataGrid (or on one of its parent)
Implement INotifyPropertyChanged on your model class, and raise PropertyChanged in the property setter.
1)
Set your window's DataContext to the GridData object:
public GridControl()
{
InitializeComponent();
GridData gd = new GridData();
gd.UpdateResults();
this.DataContext = gd;
}
2)
Implement INotifyPropertyChanged. This ensures that your view gets notified when the Results property gets updated:
public class GridData : INotifyPropertyChanged
{
private ObservableCollection<PersonName> _results;
public ObservableCollection<PersonName> Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged("GridData");
}
}
// ...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
#endregion
}
Then you can simply bind to the path relative to the data context.
<DataGrid ItemsSource="{Binding Results}" />
Note that you don't need two-way binding in this case -- that's for propagating changes from the View back to your model (ie, most useful for when there's a UI control like a text box or checkbox).
Here is an example (I used Window, but it will work the same for UserControl)
Xaml:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" ItemsSource="{Binding ElementName=UI, Path=GridData.Results, Mode=TwoWay}" />
</Grid>
</Window>
or id you want the whole DataContext:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UI">
<Grid>
<DataGrid Grid.Row="2" x:Name="datagridMain" DataContext="{Binding ElementName=UI, Path=GridData}" ItemsSource="{Binding Results}" />
</Grid>
</Window>
Code:
You will have to implement INotifyPropertyChanged so the xaml knows GridData has changed
The ObservableCollection inside GridData as this function built-in so anytime you add remove items they will update the DataGrid control
public partial class MainWindow : Window , INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GridData = new GridData { Results = new ObservableCollection<PersonName>() };
GridData.Results.Add(new PersonName { Name = "Test1" });
GridData.Results.Add(new PersonName { Name = "Test2" });
}
private GridData _gridData;
public GridData GridData
{
get { return _gridData; }
set { _gridData = value; NotifyPropertyChanged("GridData"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Classes:
I made a small change to the update method, so it just clears and updates the existing ObservableCollection, otherwise you would have to Implement INotifypropertyChanged to this class if you assign a new ObservableCollection.
public class PersonName
{
public string Name { get; set; }
}
public class GridData
{
public GridData()
{
Results = new ObservableCollection<PersonName>()
}
public ObservableCollection<PersonName> Results { get; set; }
public void UpdateResults()
{
using (EntityDataModel be = new EntityDataModel())
{
// Just update existing list, instead of creating a new one.
Results.Clear();
be.tePersons.Select(x => new PersonName { Name = x.FirstName }).ToList().ForEach(item => Results.Add(item);
}
}
}

PropertyChanged event null after setting DataContext

I am setting the DataContext for my View in the View's Constructor to an instance of my ViewModel, just standard stuff. Shortly thereafter, an UPDATE_RECENT_DOCUMENTS_LIST Event fires from the Event Aggregator which my ViewModel catches correctly. A property is changed and the onPropertyChanged method is called, but it fails as the PropertyChanged event is null.
The very next thing I do is an action to the UI which raises a CREATE_PROJECT Event and the same ViewModel is receiving events, except now, the PropertyChanged event is no longer null and everything works as expected.
Is there a specific amount of time that has to pass after setting the DataContext before it registers to the PropertyChanged Event? Is there an event I can wait for that ensures the PropertyChanged event is not null?
Also, I did not run into this problem using standard .NET events, just after integrating Prism and using the very convenient EventAggregator.
I am showing my code behind of the View and the ViewModel, omitting the View XAML for brevity.
ToolBarView.xaml.cs:
namespace ToolBarModule
{
public partial class ToolBarView : UserControl
{
public ToolBarView(ToolBarViewModel toolBarViewModel)
{
InitializeComponent();
this.DataContext = toolBarViewModel;
}
}
}
ToolBarViewModel.cs
namespace ToolBarModule
{
public class ToolBarViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ToolBarCommands baseCommands;
private IEventAggregator eventAggregator;
private KickStartEvent kickStartEvent;
private SubscriptionToken subscriptionToken;
private ObservableCollection<IDocumentReference> recentDocuments = new ObservableCollection<IDocumentReference>();
private ActionCommand newTest;
private ActionCommand openTest;
private ActionCommand saveTest;
private ActionCommand exitApplication;
public ToolBarViewModel(){}
public ToolBarViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
baseCommands = new ToolBarCommands(eventAggregator);
kickStartEvent = eventAggregator.GetEvent<KickStartEvent>();
subscriptionToken = kickStartEvent.Subscribe(kickStartEventHandler, ThreadOption.UIThread, true, toolBarEventHandlerFilter);
}
public ICommand NewTest
{
get
{
if (newTest == null)
{
newTest = new ActionCommand(baseCommands.NewTestAction);
}
return newTest;
}
}
public ICommand OpenTest
{
get
{
if (openTest == null)
{
openTest = new ActionCommand(baseCommands.OpenTestAction);
}
return openTest;
}
}
public ICommand SaveTest
{
get
{
if (saveTest == null)
{
saveTest = new ActionCommand(baseCommands.SaveTestAction);
}
return saveTest;
}
}
public ICommand ExitApplication
{
get
{
if (exitApplication == null)
{
exitApplication = new ActionCommand(baseCommands.ExitApplicationAction);
}
return exitApplication;
}
}
public ObservableCollection<IDocumentReference> RecentDocuments
{
get
{
return recentDocuments;
}
set
{
recentDocuments = value;
onPropertyChanged("RecentDocuments");
}
}
private void onPropertyChanged(string propertyChanged)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyChanged));
}
}
private void kickStartEventHandler(KickStartEventsArgs e)
{
switch (e.EventType)
{
case KickStartEventsArgs.KickStartEventType.CREATE_PROJECT:
onPropertyChanged("RecentDocuments");
break;
case KickStartEventsArgs.KickStartEventType.UPDATE_RECENT_DOCUMENTS_LIST:
RecentDocuments.Clear();
foreach (IDocumentReference recentDocs in e.KickStartTestList)
{
RecentDocuments.Add(recentDocs);
}
onPropertyChanged("RecentDocuments");
break;
}
}
}
}
You can also try to set the DataContext of a Grid or an Element below the UserControl. For me it worked.
Example (Doesn't work if you use DependencyProperty):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.MyUserControl"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
Example 2 (My working code):
Code Behind:
public MyUserControl()
{
InitializeComponent();
this.myGrid.DataContext = new { LabelText = "Hello World!" };
}
XAML
<UserControl x:Class="CoolProject.ViewModel.MyUserControl"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="myGrid">
<Label x:Name="myLabel" Content="{Binding LabelText}"/>
</Grid>
You have to name your UserControl in XAML and use it in binding. Something like following code:
<UserControl x:Name="uc" >
.
.
.
<TextBox Text="{Binding UserName, Mode=TwoWay, ElementName=uc}"/>
Where uc is a name of your UserControl, and Also try to set DataContext when UserControl loaded.
Hope this help.

Categories