MVVM pattern is implemented in my Silverlight4 application.
Originally, I worked with ObservableCollection of objects in my ViewModel:
public class SquadViewModel : ViewModelBase<ISquadModel>
{
public SquadViewModel(...) : base(...)
{
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
...
_model.DataReceivedEvent += _model_DataReceivedEvent;
_model.RequestData(...);
}
private void _model_DataReceivedEvent(ObservableCollection<TeamPlayerData> allReadyPlayers, ...)
{
foreach (TeamPlayerData tpd in allReadyPlayers)
{
SquadPlayerViewModel sp = new SquadPlayerViewModel(...);
SquadPlayers.Add(sp);
}
}
...
}
Here is a peacie of XAML code for grid displaying:
xmlns:DataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
...
<DataControls:DataGrid ItemsSource="{Binding SquadPlayers}">
...</DataControls:DataGrid>
and my ViewModel is bound to DataContext property of the view.
This collection (SquadPlayers) is not changed after its creation so I would like to change its type to
List<SquadPlayerViewModel>
. When I did that, I also added
RaisePropertyChanged("SquadPlayers")
in the end of '_model_DataReceivedEvent' method (to notify the grid that list data are changed.
The problem is that on initial displaying grid doesn't show any record... Only when I click on any column header it will do 'sorting' and display all items from the list...
Question1: Why datagrid doesn't contain items initially?
Q2: How to make them displayed automatically?
Thanks.
P.S. Here is a declaration of the new List object in my view-model:
public List<SquadPlayerViewModel> SquadPlayers { get; set; }
You can't use List as a binding source, because List not implement INotifyCollectionChanged it is require for WPF/Silverlight to have knowledge for whether the content of collection is change or not. WPF/Sivlerlight than can take further action.
I don't know why you need List<> on your view model, but If for abstraction reason you can use IList<> instead. but make sure you put instance of ObservableCollection<> on it, not the List<>. No matter what Type you used in your ViewModel Binding Only care about runtime type.
so your code should like this:
//Your declaration
public IList<SquadPlayerViewModel> SquadPlayers { get; set; }
//in your implementation for WPF/Silverlight you should do
SquadPlayers = new ObservableCollection<SquadPlayerViewModel>();
//but for other reason (for non WPF binding) you can do
SquadPlayers = new List<SquadPlayerViewModel>();
I usually used this approach to abstract my "Proxied" Domain Model that returned by NHibernate.
You'll need to have your SquadPlayers List defined something like this:
private ObservableCollection<SquadPlayerViewModel> _SquadPlayers;
public ObservableCollection<SquadPlayerViewModel> SquadPlayers
{
get
{
return _SquadPlayers;
}
set
{
if (_SquadPlayers== value)
{
return;
}
_SquadPlayers= value;
// Update bindings, no broadcast
RaisePropertyChanged("SquadPlayers");
}
}
The problem is that whilst the PropertyChanged event informs the binding of a "change" the value hasn't actually changed, the collection object is still the same object. Some controls save themselves some percieved unnecessary work if they believe the value hasn't really changed.
Try creating a new instance of the ObservableCollection and assigning to the property. In that case the currently assigned object will differ from the new one you create when data is available.
Related
I am creating a simple WPF app that when you click on a button it runs through a few steps like copy the file to a new location, convert the file then it copies the new file back to the original location.
The steps are working fine but I would like to have the WPF window update to which step it is on and hide the button while it is running.
The window only updates once it has finished running my code. I think I used to be able to do this on classic forms with me.refresh but this doesn't work on WPF.
Is something I can do to update the window after each step is complete?
Thank you
Button1.Visibility = Windows.Visibility.Hidden
FileCopy("C:\Test.xsf", AppPath & "\Convert\test.xsf")
Image7.Visibility = Windows.Visibility.Hidden
Image3.Visibility = Windows.Visibility.Visible
Program.StartInfo.FileName = xDefs
Program.StartInfo.Arguments = "/q"
Program.Start()
Program.WaitForExit()
Image5.Visibility = Windows.Visibility.Visible
FileCopy("AppPath & "\Convert\test.csv, "C:\Test.csv")
Button1.Visibility = Windows.Visibility.Visible
In order to update the UI while your program is busy, you'll need to use the Dispatcher class to add your update request onto the UI message queue. Take this synchronous example:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
ConvertFile(filePath);
CopyFileBack();
}
We could use the Dispatcher class to break this up and feed messages back to the UI in between tasks:
public void DoWorkWithFile(string filePath)
{
CopyFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied" });
ConvertFile(filePath);
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Converted" });
CopyFileBack();
RunOnUiThread((Action)delegate { SomeUiTextBlock.Text = "Copied back" });
}
private object RunOnUiThread(Action method)
{
return Dispatcher.Invoke(DispatcherPriority.Normal, method);
}
I know this is a VB.NET tagged question but I'll just go ahead and share a C# solution. I hope you know enough of it to port it to VB. This is the first time and posting anything to stackoverflow, if it solves your problem please mark it as the answer :-)
You must first know a thing or two (actually a lot more) on data binding. You basically create a view model, define the property that changes with time and bind this to the window. In this case you must define a value to keep track of the current operation and let the button control.
Disclaimer, I wrote this in notepad and haven't tested it on visual studio. Be on the lookout for typos.
using System.ComponentModel;
namespace FileConverter
{
//define the various states the application will transition to
public enum OperationStatus
{
CopyingFileToNewLocation
ConvertingFile,
CopyingFileToOriginalLocation
OperationCompelete
}
//Defines the view model that shall be bound to the window.
//The view model updates the UI using event notifications. Any control that had enabled
//binding will get updated automatically
public class ViewModel : INotifyPropertyChanged//This interface defines an event used to raise an event and notify subscribers of a changed in data
{
private OperationStatus _FileConvertionStatus;
public event PropertyChangedEventHandler PropertyChanged;
public OperationStatus FileConvertionStatus
{
get
{
return _FileConvertionStatus;
}
set
{
_FileConvertionStatus=value;
//Notify all UIElements / objects that had subscribed to this property that it has changed
RaisePropertyChanged(this,"FileConvertionStatus");
}
}
public void RaisePropertyChanged(object sender,string propertyName)
{
//check if there is any object that had subscribed to changes of any of the data properties in the view model
if(PropertyChanged!=null)
PropertyChanged(sender,new PropertyChangedEventArgs(propertyName));
}
public void StartFileConvertion(string filePath)
{
//Any time we change the property 'FileConvertionStatus', an event is raised which updates the UI
this.FileConvertionStatus=OperationStatus.CopyingFileToNewLocation;
StartCopyingToNewLocation(); //call your copying logic
this.FileConvertionStatus=OperationStatus.ConvertingFile;
StartFileConvertion(); //call your conversion logic
this.FileConvertionStatus=OperationStatus.CopyingFileToOriginalLocation();
CopyFileToOriginalLocation(); //...
this.FileConvertionStatus=OperationStatus.OperationCompelete;
}
}
}
//Now for the UI section
In the constructor of the window, you must bind the window to the view model right after this window has been initialized
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel vm=new ViewModel();
//setting the data context property the window implicitly binds the whole window to our view model object
this.DataContext=vm;
string filePath="c:\file.txt";
//start the file manipulation process
vm.StartFileConvertion(filePath);
}
}
//Next step we need to bind the button to the 'FileConvertionStatus' property located in the view model. We don't bind the button to the whole view model, just the property that it's interested in. Having bound the window to the view model in the previous code, all child elements get access to the public properties of this view model (VM from now on). We do the property binding in XAML
..Button x:Name="btnStartFileProcessing" Enabled="{Binding FileConvertionStatus}"...
We're almost there. One this is missing. You'll notice that the 'Enabled' property is a Boolean value. The 'FileConvertionStatus' property is enum. Same way you can't assign an enum to a Boolean directly, you need to do some convertion. This is where converters come in.
Converters allow you to define how one property can be converted to a different one in XAML. In this case we want the button to be enabled only when file conversion is successful. Please do some reading into this.
Create a class as shown below:
using System.Windows.Data;
namespace FileConverter
{
public class OperationStatusToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
OperationStatus status=(OperationStatus)value;
switch(status)
{
case OperationStatus.OperationCompelete:
return true; //enable the button when everything has been done
default:
return false;//disable the button as the file processing is underway
}
}
public object ConvertBack(object value, Type targetType,object parameter,System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Next step is to define the converter in XAML code. Think of this as initializing it though it can't be further from the true :-). Its more of importing the namespace into the xaml.Put the code below in the App.XAML file. Doing such declaration in the App.XAML file makes the code visible globally.
xmlns:MyConverters="clr-namespace:FileConverter"
In the Application.Resources XAML tag, declare the converter as shown below
<Application.Resources>
<MyConverters:OperationStatusToBooleanConverter x:Key="OperationStatusToBooleanConverter"/>
</Application.Resources>
Final Step
Redo the binding code in the button to include the converter.
...Button Enabled="{Binding FileConvertionStatus,Converter={StaticResource OperationStatusToBooleanConverter}}" x:Name="btnStartFileProcessing" ...
Please note that I haven't thread-optimized this code, the main problem is that all work is being done on the UI thread which can lead to the window hanging if an operation takes long.
The amount of work needed to properly set the binding up as per MVVM code standards is a lot. It might seem like an over-kill and at times, it actually is. Keep this in mind though, once the UI gets complex MVVM will definitely save the day due to the separation of concerns and binding strategies.
I've got the following question: what's the expected scenario for the logic when I would want to bind some elements inside of ViewModel separatly. What I mean...
http://slodge.blogspot.co.uk/2013/04/n3-kitten-cells-on-iphone-n1-days-of.html
There is a "Kitten" class in the sample provided - this is just a common "DTO" object.
And there is also a modelview class which contains those objects list:
public List<Kitten> Kittens
{
get ...
set { ... RaisePropertyChanged(() => Kittens); }
}
We can bind a grid with cells (which bound to Kitten properties). But what if I would want to be able to activate RaisePropertyChanged on every property of Kitten separatly? I.e.,
if the kitten Title changed, then to call RaisePropertyChanged (and accordingly, change only bound cell value instead of the whole list refreshing) on KittenTitle property (for instance)?
The sample with Kittens is obviously primitive and does not need such implementation, but what if instead of Kittens I would have a list similar to Facebook App menu panel, where there are menu items (amount of which can vary) and those items can have "Notifications Count" label (or can not) so, instead of the complete refresh of the list, how can I initiate that label only refreshing (caused by related property inside the "Kitten" instance changed)?
(That looks like viewModel inside viewModel for me, but not sure how to solve it smarter with MvvmCross).
Thank you!
You can implement Nested INotifyPropertyChanged objects - exactly as you do in Windows binding.
So if one Kitten raises its property changed then only that part of the UI for that kitten will refresh
e.g. a Kitten could be written:
public class DynamicKitten : MvxNotifyPropertyChanged // can use MvxViewModel as base class if preferred
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged(() => Name); }
}
}
For some examples of this - mostly using Linq to wrap static objects - see:
https://github.com/slodge/MvvmCross-Tutorials/tree/master/InternetMinute
https://github.com/slodge/MvvmCross-Tutorials/tree/master/MonoTouchCellTutorial (discussed pre-v3 in http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html)
https://github.com/slodge/MvvmCross-Tutorials/tree/master/Sample%20-%20CirriousConference
One of my favorite StackOverflow libraries took this INPC approach all the way back to the Json layer - take a look at all the INPC entities in https://stacky.codeplex.com/SourceControl/latest#trunk/source/Stacky/Entities/Answer.cs
May be it's a silly (or more than trivial) kinda question, but it seems i just don't know the answer. Here's the case -
I assigned a UserList as the ItemsSource of a combobox. So what i did essentially is assigning a reference type to another.
I cleared the UserList. So now i get the Count of the ItemsSource 0 as well.
I still get the items present in my combobox. And i also can cast the SelectedItem of the combobox to a User object.
Here's the complete code -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
private List<User> _userList;
public MainWindow()
{
InitializeComponent();
_userList = new List<User>()
{
new User() {Id = 1, Name = "X"},
new User() {Id = 2, Name = "Y"},
new User() {Id = 3, Name = "Z"}
};
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBox1.ItemsSource = _userList;
this.comboBox1.DisplayMemberPath = "Name";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_userList.Clear();
/* ItemsSource is cleared as well*/
IEnumerable userList = this.comboBox1.ItemsSource;
/*I can still get my User*/
User user = this.comboBox1.SelectedItem as User;
}
}
So, where the items are coming from? What actually happens under-the-hood when i make such binding? Does the control have some kind of cache? It's a royal pain to realize not having such basic ideas. Can anybody explain the behind-the-scene detail?
EDIT : I wrote the code in WPF, but i have the same question for WinForms Combobox.
EDIT : Doesn't a combobox display its items from it's in-memory Datasource? When that datasource contains 0 items, how does it display the items?
When you set an ItemsSource of any ItemsControl it copies the ref to the list into its Items property. Then it subscribes to the OnCollectionChanged event, and creates a CollectionView object. So, on the screen you can see that collectionView.
as I have found in source code ItemCollection holds two lists:
internal void SetItemsSource(IEnumerable value)
{
//checks are missed
this._itemsSource = value;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
}
How could you get SelectedItem?
This is my assumption from quick look into the source code:
ItemsControl has a collection of "views" and each View sholud store a ref to the item (User instance), because it has to draw data on the screen. So, when you call SelectedItem it returns a saved ref.
Upd about references
Assume there is an User instance. It has the adress 123 in memory. There is a list. It stores references. One of them is 123.
When you set an ItemsSource ItemsControl saves a reference to the list, and creates a Views collection. Each view stores a references to an item. One view stores an address 123.
Then you cleared a list of users. Now list doesn't contains any references to Users. But in memory there is an adrress 123 and there is an instance of User by this adress. Garbage Collector doesn't destroy it, because View has a reference to it.
When you get SelectedItem it returns User instance from the 123 adress.
var user = new User();
var list = new List<User>();
list.Add(user);
list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty
Console.WriteLine(user == null); //prints false. - user instance is sill exists;
In answer to your comment to #GazTheDestroyer ("... why it doesn't get cleared, and how it holds the items?")
In WPF, when you set the ItemsSource property of an ItemsControl, the control will wrap the list of items in a CollectionView, which is a collection type optimised for use by the UI framework. This CollectionView is assigned to the Items property of the control and is what the display-drawing code actually works from. As you see, this collection is entirely separate of the object you originally assigned to ItemsSource, and so there is no propogation of changes from one to the other. This is why the items are still in the control when you clear the original list: the control is ignoring the original list, and has its own list that contains your objects.
It's for this reason that an ItemsSource value needs to raise events - specifically INotifyCollectionChanged.NotifyCollectionChanged - so that the control knows to refresh the Items list. ObservableCollection implements this interface and raises the correct event, and so the functionality works as expected.
It's hugely important to note that this is nothing like what happens in WinForms, which is why I've been pressing you for the clarification.
EDIT: To clarify, there is no "deep copy." The code that is happening is similar in principle to the following:
private List<object> myCopy;
public void SetItemsSource(List<object> yourCopy)
{
myCopy = new List<object>();
foreach (var o in yourCopy)
{
myCopy.Add(o);
}
}
Once this code has run, there's only one copy of every item in your list. But each of the items is in both of the lists. If you change, clear or otherwise manipulate yourCopy, myCopy knows nothing about it. You cannot "destroy" any of the objects that are within the list my clearing yourCopy - all you do is release your own reference to them.
Assuming you are using WPF:
List<User> doesn't fire any event that the UI will recognise to refresh itself. If you use ObservableCollection<User> instead, your code will work.
The key difference is that ObservableCollection implements INotifyCollectionChanged, which allows the UI to recognise that the content of the collection has changed, and thus refresh the content of the ComboBox.
(Note that this does not work in WinForms. In WinForms you can set the DataSource property of the control, but the same ObservableCollection trick does not work here.)
When you set a collection reference to ItemsControl, all the combo gets is a reference, that it knows is enumerable.
It will enumerate the reference and display the items. Whether it does a deep copy or shallow copy is irrelevant, all it has is a reference (memory address effectively).
If you change your collection in some way, the combo has no way of knowing unless you tell it somehow. The reference (address) hasn't changed, everything looks the same to the combo. You seem to be thinking that the object is somehow "live" and the combo can watch the memory changing or something? This isn't the case. All it has is a reference that it can enumerate over. The contents can change but without some trigger the combo doesn't know that, and so will sit doing nothing.
ObservableCollection is designed to overcome this. It implements INotifyCollectionChanged that fires events when it changes, so the Combo knows that it must update its display.
I have a ViewModel class that looks like this.
class MyViewModel : Screen
{
public BindableCollection<MyObject> MyObjects { get; set; }
private MyObject selectedObject;
public MyObject SelectedMyObject
{
get { return selectedObject; }
set
{
selectedObject = value:
//some additional unrelated logic
}
}
public void SaveObject()
{
//some logic
}
public bool CanSaveObject{
get{
//logic to determine if the selectedObject is valid
}
}
That is the relevant code. Now the problem.
MyObject is a class with three properties. In the View I have a ListView that is bound to the MyObjects collection, and three TextBoxes that are bound to the SelectedItem in the ListView.
When I fill in the textboxes, the related object gets changed in the Model, but I want to make sure that the object is in a valid state before you can save it. CanSaveObject has the necessary logic, but the problem is that is never gets called since I don't have any oppurtunity to call NotifyOfPropertyChanged when the textboxes are filled since only the properties of selectedObject are called, and no properties on MyViewModel.
So the question is: Are there any good way to do this without making properties on the ViewModel that encapsulate the properties inside MyObject.
I have got it working if I make properties like these, and then bind to these instead of the SelectedItem directly in the view, but the viewmodel gets cluttered up in the hurry if hacks like this is the only way to do it. I hope it's not :)
public string SelectedObjectPropertyOne{
get{ return selectedObject.PropertyOne; }
set{
selectedObject.PropertyOne = value;
NotifyOfPropertyChange(() => SelectedObjectPropertyOne);
NotifyOfPropertyChange(() => CanSaveObject);
}
}
ActionMessage.EnforceGuardsDuringInvocation is a static boolean field that can be set to enforce a guard check when an action is about to be invoked. This will guard the actual Save action from being invoked, however it will not help with the issue of the UI appearance based on the guard state immediately after an update to the selected model.
Without doing that, the only other modification I could suggest would be to create a VM type for MyObject model and move the validation and save logic there. This would also allow you to simplify your Views...
I noticed that ObservableCollection in WPF reflects changes in GUI only by adding or removing an item in the list, but not by editing it.
That means that I have to write my custom class MyObservableCollection instead.
What is the reason for this behaviour?
Thanks
The ObservableCollection has no way of knowing if you make changes to the objects it contains - if you want to be notified when those objects change then you have to make those objects observable as well (for example by having those objects implement INotifyPropertyChanged)
another way of achieving this would be that you implement a new XXXViewModel class that derives from DependencyObject and you put this one in the ObservableCollection.
for this look at this very good MVVM introduction: http://blog.lab49.com/archives/2650
an example for such a class would be:
public class EntryViewModel : DependencyObject
{
private Entry _entry;
public EntryViewModel(Entry e)
{
_entry = e;
SetProperties(e);
}
private void SetProperties(Entry value)
{
this.Id = value.Id;
this.Title = value.Title;
this.CreationTimestamp = value.CreationTimestamp;
this.LastUpdateTimestamp = value.LastUpdateTimestamp;
this.Flag = value.Flag;
this.Body = value.Body;
}
public Entry Entry
{
get {
SyncBackProperties();
return this._entry;
}
}
public Int64 Id
{
get { return (Int64)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
// Using a DependencyProperty as the backing store for Id. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(Int64), typeof(EntryViewModel), new UIPropertyMetadata(new Int64()));
}}
important things here:
- it derives from DependencyObject
- it operates with DependencyProperties to support WPFs databinding
br
sargola
You can register a method in the view model class aginst the PropertyChanged event of data class objects and listen to them in View model when any change in the property of the data objects happen. This is very easy and straight way to have the control in View model when the items of an observable collection changes. Hope this helps...
Probably because items have no way to alert the collection when they are edited - i.e. they might not be observable. Other classes would have similar behavior - no way to alert you to a every change in the graph of referenced classes.
As a work-around, you could extract the object from the collection and then reinsert it after you are done processing. Depending on your requirements and concurrency model, this could just make the program ugly, though. This is a quick hack, and not suitable for anything that requires quality.
Instead, you could implement the collection with an update method that specifically triggers the ContentChanged (not sure about the name) event. It's not pretty, but it is at least quite easy to deal with.
Ideally, as kragen2uk says, it would be best to make the objects observable and keep your client code clean and simple.
see also this question.