Understanding MVVM and ObjectDetailViewModel in Udemy Course - c#

I've been doing a course for Xamarin and I am at the MVVM portion where all the xaml.cs code is moved to ViewModels.
This is the course (lecture #104, 105, 106): [url]https://www.udemy.com/course/complete-xamarin-developer-course-ios-and-android[/url]
So far I've been able to understand the concept of MVVM, however, the course seems to do a few things incorrectly (as I believe) and some of the code in previous lectures isn't in the new lectures (we'll leave that annoyance alone for now), moving forward, I am not sure how to code it correctly. I also understand that I am presenting a simple use case at the moment so let's think that Post is a huge object that shouldn't be re-created over and over.
For example, I have a PostDetailPage.xaml(.cs), in the xaml, I currently have the following (note that most of the Mode=TwoWay might not be used properly):
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5"></Entry>
<label x:name="venuename"
text="{binding post.venuename, mode=onetime}"
fontattributes="bold"/>
<label x:name="categoryname"
text="{binding post.categoryname, mode=onetime}"/>
<label x:name="address"
text="{binding post.address, mode=onetime}"/>
<label x:name="coordinatelabel"
text="{binding coordinates, mode=onetime}"/>
<label x:name="distance"
text="{binding post.distance, mode=onetime, stringformat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding Post}" />
</StackLayout>
</ContentPage.Content>
Code behind is the following:
public partial class PostDetailPage : ContentPage
{
PostDetailViewModel viewModel;
public PostDetailPage(Post selectedPost)
{
InitializeComponent();
viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Then I have an incomplete PostDetailViewModel:
public class PostDetailViewModel : INotifyPropertyChanged
{
public UpdatePostCommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new UpdatePostCommand(this);
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
OnPropertyChanged("Post");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
OnPropertyChanged("Experience");
}
}
private void UpdatePostObject() {
Post = new Post() {
Experience = experience,
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
}
}
private void UpdatePostObject()
{
//This is used to Trigger the change on Post
//I don't think this is the best way (hence my question(s))
SelectedPost = new Post()
{
Experience = experience
};
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
From the course, the instructor recommends that when Experience is changed (or any other property related to Post), that we re-create the Post object again and again to trigger CanExecute and associated Execute. This seems incorrect to have to re-create the Post object again and again after each change, so would updating only what is needed be best?
So, what I am asking (or learning about) is...
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
Please let me know if there is anything else I can provide.
Thank you
Derek

First of all I cannot open your url, this course is missing.
Should I implement a generic PostViewModel that is updaded when Experience has changed and somehow attach the ICommand to it, if so, how would something like that look like? Of course, if this is not correct, please point me in the right direction if possible.
For the UpdatePostObject method, you do not need to new a Post object, If you want to set the new value for the selectedPost object, just set it like following code directly,
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
If you want to achieve the click command, you just declear the public ICommand UpdatePostCommand { get; set; } in PostDetailViewModel. Then achieve it in your PostDetailViewModel's constructor. Here is my all code about PostDetailViewModel
public class PostDetailViewModel
{
//: INotifyPropertyChanged
public ICommand UpdatePostCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PostDetailViewModel(Post post)
{
UpdatePostCommand = new Command<Post>((key) => {
key.categoryname = "Command categoryname";
key.distance = "31";
key.address = "Command address";
key.venuename = "Command venuename";
key.Experience = "Command Experience";
});
SelectedPost = post;
}
//Can this become another ViewModel or ?
private Post selectedPost;
public Post SelectedPost
{
get { return selectedPost; }
set
{
selectedPost = value;
// OnPropertyChanged("SelectedPost");
}
}
private string experience;
public string Experience
{
get { return experience; }
set
{
experience = value;
UpdatePostObject();
// OnPropertyChanged("Experience");
}
}
private void UpdatePostObject()
{
selectedPost.Experience = experience;
selectedPost.address= "new Adree";
selectedPost.categoryname = "new CateGoryName";
selectedPost.distance = "41";
selectedPost.venuename = "new venu";
//many more properties are needed here
//seems like if the solution was to grow,
//this would become unmanagable or something
// };
}
//private void UpdatePostObject()
//{
// //This is used to Trigger the change on Post
// //I don't think this is the best way (hence my question(s))
// SelectedPost = new Post()
// {
// Experience = experience
// };
//}
//public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
//{
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
//}
public async void Update()
{
await App.Current.MainPage.Navigation.PushAsync(new HistoryPage());
}
}
}
If you want to achieve the attribute change at the running time, you should achieve the INotifyPropertyChanged interface for all of your attribute in your POST model.
public class Post:INotifyPropertyChanged
{
// public string _venuename { get; set; }
// public string categoryname { get; set; }
string _categoryname;
public string categoryname
{
set
{
if (_categoryname != value)
{
_categoryname = value;
OnPropertyChanged("categoryname");
}
}
get
{
return _categoryname;
}
}
//public string address { get; set; }
string _address;
public string address
{
set
{
if (_address != value)
{
_address = value;
OnPropertyChanged("address");
}
}
get
{
return _address;
}
}
// public string Experience { get; set; }
string _experience;
public string Experience
{
set
{
if (_experience != value)
{
_experience = value;
OnPropertyChanged("Experience");
}
}
get
{
return _experience;
}
}
// public string distance { get; set; }
string _distance;
public string distance
{
set
{
if (_distance != value)
{
_distance = value;
OnPropertyChanged("distance");
}
}
get
{
return _distance;
}
}
string _venuename;
public string venuename
{
set
{
if (_venuename != value)
{
_venuename = value;
OnPropertyChanged("venuename");
}
}
get
{
return _venuename;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And you layout have some errors, I change of them.
<ContentPage.Content>
<StackLayout Margin="10">
<Entry x:Name="experienceEntry"
Text="{Binding Experience, Mode=TwoWay}"
Margin="-5">
</Entry>
<Label x:Name="venuename"
Text="{Binding SelectedPost.venuename, Mode=TwoWay}"
FontAttributes="Bold"
/>
<Label x:Name="categoryname"
Text="{Binding SelectedPost.categoryname, Mode=TwoWay}"/>
<Label x:Name="address"
Text="{Binding SelectedPost.address, Mode=TwoWay}"/>
<Label x:Name="coordinatelabel"
Text="{Binding SelectedPost.coordinates, Mode=TwoWay}"/>
<Label x:Name="distance"
Text="{Binding SelectedPost.distance, Mode=TwoWay, StringFormat='{0:0}'}"/>
<Button Text="Update"
x:Name="updateButton"
Command="{Binding UpdatePostCommand}"
CommandParameter="{Binding SelectedPost}" />
</StackLayout>
</ContentPage.Content>
Here is my layout background code.
public MainPage()
{
InitializeComponent();
Post selectedPost=new Post() {
address= "address",
categoryname= "categoryname",
distance= "21",
venuename= "venuename" };
PostDetailViewModel viewModel = new PostDetailViewModel(selectedPost);
BindingContext = viewModel;
}
}
Is there some type of OnObjectChanged like method that could be used? I know of the ObservableCollection when dealing with collections.
In your PostDetailViewModel it is no need to achieve that. If you want to use ObservableCollection to add Post , it achieve the INotifyPropertyChanged, If you Post items will increate or decrease, it will change automatically, But if value of Post will be changed, it will not change, you have to achieve the INotifyPropertyChanged in your Post model.
Here is my running GIF.Normally, it have some default value, if I enter the value in the Entry, this value of post will be changed, if I click the Button, the value of post will be changed to another value
Here is a helpful article about it, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

Related

C# How to edit cell value in gridview?

I have a gridview shown as below in XAML
<ListView x:Name="listTasks">
<ListView.View>
<GridView x:Name="gridTasks">
<GridViewColumn Header="ID" HeaderStringFormat="Lowercase" Width ="26" DisplayMemberBinding="{Binding id}"/>
<GridViewColumn Header="Something" Width="113" DisplayMemberBinding="{Binding something}"/>
<GridViewColumn Header="State" Width="179" DisplayMemberBinding="{Binding currentState}"/>
</GridView>
</ListView.View>
</ListView>
and i have a button which adds to this gridview using the below
m.myList.Add(new mylistview.myitems
{
id = m.id,
something= m.something,
currentState = m.currentState,
});
This button works perfectly by adding the row into the gridview. However I would like to modify theCurrentState using a method that is running. How would I locate for example, ID = "8" and then modify theCurrentState for that row?
UPDATED CODE SHOWN
I've now replaced my list<Task> with ObservableCollection and managed to get it to add to my listview when I click onto my button. However, I am struggling to implement the iNotifyPropertyChanged into my code and getting it to work correctly... Below is my listview class
public class mylistview : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _currentState;
public string currentState
{
get { return _currentState; }
set
{
_currentState = value;
OnPropertyChanged();
}
}
public ObservableCollection<myitems> _myList = new ObservableCollection<myitems>();
public ObservableCollection<myitems> myList
{
get { return _myList; }
}
private static int _id = 0;
public class myitems
{
public int id { get; set; }
public string something{ get; set; }
public string currentState { get; set; }
}
public int id
{
get { return _id; }
set { _id = value; }
}
}
So I see you're using data bindings already, that's good. But your question makes me think you haven't quite grasped everything it can do for you yet.
My recommendation would be to forget about adding items directly to listOfTasks.Items. Instead you should make an ObservableCollection to hold that list and bind the listOfTasks to it. Like so:
ObservableCollection tasks = new ObservableCollection<mylistview.myitems>();
ListOfTasks.ItemsSource = tasks;
With that binding in place you should be able to simply add new items to the tasks list when they click your button:
tasks.Add(new mylistview.myitems
{
id = theId,
something= something,
currentState = theCurrentState,
});
and it should automatically update the GUI.
The last step is to make sure that the class mylistview.myitems implements INotifyPropertyChanged. This is easier than it sounds; you just need to have it trigger an event any time the property is set. Something like so:
public class exampleProperties: INotifyPropertyChanged
{
//this is the event you have to emit
public event PropertyChangedEventHandler PropertyChanged;
//This is a convenience function to trigger the event.
//The CallerMemberName part will automatically figure out
//the name of the property you called from if propertyName == ""
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
//Any time this property is set it will trigger the event
private string _currentState = "";
public string currentState
{
get { return _currentState; }
set
{
if (_currentState != value)
{
_currentState = value;
OnPropertyChanged();
}
}
}
}
Now that the gridview is bound to an ObservableCollection and the items held in that collection can notify interested GUI controls that their properties have changed, you should simply be able to update the GUI simply by changing the appropriate item in the collection.
And here's an example of a form that uses the whole technique: https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).asp
edit
I forgot that you specifically need to bind to the ItemSource property of the ListView. The way I have done it in the past is to set ItemsSource={binding} in the ListView's xaml and then assign an ObservableCollection to ListView.DataContext. However I have found an easier way and updated the original post with it. Here's a reference: http://www.wpf-tutorial.com/listview-control/listview-with-gridview/
Edit 2
Aha, you're adding the iPropertyChangedNotify to the wrong thing. It goes on the myitems class like so:
public class myitems : iNotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string something{ get; set; }
public string currentState { get; set; }
}
I leave updating the current state and something properties as an excersize. They also need to trigger the OnPropertyChanged event when their value is set.
Maybe with
listOfTasks.Items.Cast<ListViewItem>().First(item => item.ID == "8").theCurrentState = newState;
//I'm not sure about the Cast stuff, because I don't know what types the ListView uses for its items
Of course you could iterate through the items with a loop and check manually for the ID as well.

UWP MVVM Data Binding for dummies (textbox.text from String)

Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";

MVVM - Binding and changing

Actual state: I click in the Login Button and the ViewModel changes to the new View.
Desired state: I click the LoginButton (the LoginViewModel binds the with the view to get the Email and Password and verifies in the server the authenticity of the user and if its ok the request receives as answer the info about the user and changes the view)
What I know: change the views, bind the textbox, communication with the server (handling the request and the answers)
What I don't know: send from the LoginViewModel to the GeneralViewModel the answer with the info about the user, don't know how to maintain the PasswordBox instead of the TextBox for binding.
CODE:
LoginView
<Grid Margin="0,0,-74.4,-11.8" HorizontalAlignment="Left" Width="800" Height="600" VerticalAlignment="Top">
<TextBox Text = "{Binding Email, Mode = TwoWay}" Style="{DynamicResource MyTextBox}" x:Name="textBoxEmail" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Center" HorizontalAlignment="Center" Width="248" Margin="274,212,278,347" FontFamily="Segoe UI Semibold" />
<Image Source="C:\Users\Images\logo.png" x:Name="Logo" HorizontalAlignment="Left" Height="129" Margin="301,63,0,0" VerticalAlignment="Top" Width="151" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0.091"/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
Sign up now!
Forgot your Password? Click here!
LoginViewModel
class LoginViewModel : AViewModel
{
WifiAP wa;
#region fields
private string _email = null;
private TokenRequest tk;
public DelegateCommand LoginCommand { get; set; }
public string Email
{
get
{
return _email;
}
set
{
_email = value;
OnPropertyChanged("Email");
//Here's the magic
LoginCommand.RaiseCanExecuteChanged();
}
}
private string _password = null;
public string Password
{
get
{
return _password;
}
set
{
_password = value;
OnPropertyChanged("Password");
//Here's the magic
LoginCommand.RaiseCanExecuteChanged();
}
}
public string mac;
#endregion
public LoginViewModel()
{
wa = new WifiAP();
LoginCommand = new DelegateCommand(Login, CanLogin);
}
public bool CanLogin()
{
return !string.IsNullOrEmpty(Email);
}
public void Login()
{
//
}
#region auxiliaryMethods
public string getMac()
{
mac = wa.GetMACAddress();
return mac;
}
public string hashingMD5(string pass)
{
string pwd = pass;
System.Security.Cryptography.MD5 hs = System.Security.Cryptography.MD5.Create();
byte[] db = hs.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pwd));
string result = Convert.ToBase64String(db);
return result;
}}
MainViewModel
public MainWindowViewModel{
this.AddViewModel(new LoginViewModel() { DisplayName = "Login", InternalName = "LoginViewModel" });
this.AddViewModel(new GeneralViewModel() { DisplayName = "General", InternalName = "GeneralViewModel" });
this.Current_ViewModel = this.GetViewModel("LoginViewModel");
Thanks in advance for your time.
At first, here you have the article that tells you what and how to cope with PasswordBox in MVVM. Secondly, how to pass the data through? I'm not very familiar with MVVM Light because I personally use PRSIM. You may though do something alike RegionContext.
At first you have to create a class as your RegionData. Notice, that this model also implements the OnPropertyChanged interface (the implementation comes form inherited ViewModelBase in MVVM Light).
public class HaveLoginData : ViewModelBase
{
private string _login;
public string Login
{
get { return _login; }
set
{
_login = value;
RaisePropertyChanged(() => Login);
}
}
}
Than, in the constructor you should make a common instance of this class for both of your ViewModels:
public MainWindowViewModel
{
var regionData = new HaveLoginData();
this.AddViewModel(new LoginViewModel() { RegionData = regionData });
this.AddViewModel(new GeneralViewModel() { RegionData = regionData });
this.Current_ViewModel = this.GetViewModel("LoginViewModel");
}
You will also have to add a new property to your ViewModels called RegionData:
using System.ComponentModel;
namespace YourApp.ViewModels
{
public class GeneralViewModel
{
private HaveLoginData _regionData;
public HaveLoginData RegionData
{
get { return _regionData; }
set
{
_regionData = value;
_regionData.PropertyChanged += _regionData_PropertyChanged;
}
}
private void _regionData_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Login")
{
// do the second view model login logic
}
}
}
}
And the Login ViewModel:
namespace YourApp.ViewModels
{
public class LoginViewModel
{
public HaveLoginData RegionData { get; set; }
public void Login()
{
// do the login conditions logic
if (true)
{
RegionData.Login = "new user login";
}
}
}
}
As you can see, when someone will set the RegionData, you will subscribe to the PropertyChanged event and be noticed in every ViewModel about the changes inside the Login and MD5Password properties. As you see, you also have to remeber about unsubscribing from the PropertyChanged event from your previous RegionData. The RegionData once created should not be changed, so it may not be neccessary to do it in the setter (you can make some kind of Dispose to delete the reference).

How to populate a combobox with access using MVVM

I'm new to C# and I'm trying to create a code using MVVM pattern, but I don't know how to populate a combobox using that pattern. Please Give me help to create the ViewModel and the binding to the xaml.
Code Model:
public int Cd_Raca
{
get;
set
{
if(Cd_Raca != value)
{
Cd_Raca = value;
RaisePropertyChanged("Cd_Raca");
}
}
}
public string Nm_Raca
{
get;
set
{
if(Nm_Raca != value)
{
Nm_Raca = value;
RaisePropertyChanged("Nm_Raca");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml:
<ComboBox x:Name="dsCmbRaca" HorizontalAlignment="Left" Margin="438,4,0,0"
VerticalAlignment="Top" Width="94" Height="19"/>
Use the ItemsSource Property and set it to an enumeration of objects. With DisplayMemberPath you can set it to a property of a single object of your list if the list is not just a list of strings.
I.e. in my sample the object of the list has a Description property for display and a Value property for the selected value.
All bindings in the sample need to be a property in your ViewModel (=DataContext).
<ComboBox DisplayMemberPath="Description" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="120"
ItemsSource="{Binding myList}"
SelectedValue="{Binding mySelectedValue}" SelectedValuePath="Value" />
Edit:
The List property could look like this:
public IList<MyObject> myList { get { return new List<MyObject>();} }
The Object could look like this for example:
public class MyObject
{
public string Description { get; }
public enum Value { get;}
}
The Object is optional. You could just pass a list of strings.
Disclaimer: I hacked this in notepad. I hope it compiles.
UPDATE
Looking at your code at least from what you post your properties are not implemented correctly. You need a backing field if you code it like you have:
private int _cd_Raca;
private string _nm_Raca;
public int Cd_Raca
{
get{ return _cd_Raca;}
set
{
if(_cd_Raca != value)
{
_cd_Raca = value;
RaisePropertyChanged("Cd_Raca");
}
}
}
public string Nm_Raca
{
get{return _nm_Raca;}
set
{
if(_nm_Raca != value)
{
_nm_Raca = value;
RaisePropertyChanged("Nm_Raca");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Reading your comment to my first answer seems you might have a specific use case. So if this update does not help maybe you can add some more information to your question.

MVVM - implementing 'IsDirty' functionality to a ModelView in order to save data

Being new to WPF & MVVM I struggling with some basic functionality.
Let me first explain what I am after, and then attach some example code...
I have a screen showing a list of users, and I display the details of the selected user on the right-hand side with editable textboxes. I then have a Save button which is DataBound, but I would only like this button to display when data has actually changed. ie - I need to check for "dirty data".
I have a fully MVVM example in which I have a Model called User:
namespace Test.Model
{
class User
{
public string UserName { get; set; }
public string Surname { get; set; }
public string Firstname { get; set; }
}
}
Then, the ViewModel looks like this:
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;
namespace Test.ViewModel
{
class UserViewModel : ViewModelBase
{
//Private variables
private ObservableCollection<User> _users;
RelayCommand _userSave;
//Properties
public ObservableCollection<User> User
{
get
{
if (_users == null)
{
_users = new ObservableCollection<User>();
//I assume I need this Handler, but I am stuggling to implement it successfully
//_users.CollectionChanged += HandleChange;
//Populate with users
_users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
_users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
}
return _users;
}
}
//Not sure what to do with this?!?!
//private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
//{
// if (e.Action == NotifyCollectionChangedAction.Remove)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Removed items
// }
// }
// else if (e.Action == NotifyCollectionChangedAction.Add)
// {
// foreach (TestViewModel item in e.NewItems)
// {
// //Added items
// }
// }
//}
//Commands
public ICommand UserSave
{
get
{
if (_userSave == null)
{
_userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
}
return _userSave;
}
}
void UserSaveExecute()
{
//Here I will call my DataAccess to actually save the data
}
bool UserSaveCanExecute
{
get
{
//This is where I would like to know whether the currently selected item has been edited and is thus "dirty"
return false;
}
}
//constructor
public UserViewModel()
{
}
}
}
The "RelayCommand" is just a simple wrapper class, as is the "ViewModelBase". (I'll attach the latter though just for clarity)
using System;
using System.ComponentModel;
namespace Test.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
protected ViewModelBase()
{
}
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);
}
}
public void Dispose()
{
this.OnDispose();
}
protected virtual void OnDispose()
{
}
}
}
Finally - the XAML
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Test.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:UserViewModel/>
</Window.DataContext>
<Grid>
<ListBox Height="238" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox1" VerticalAlignment="Top"
Width="197" ItemsSource="{Binding Path=User}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Firstname}"/>
<TextBlock Text="{Binding Path=Surname}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="232,16,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,21,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/UserName}" />
<Label Content="Surname" Height="28" HorizontalAlignment="Left" Margin="232,50,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,52,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Surname}" />
<Label Content="Firstname" Height="28" HorizontalAlignment="Left" Margin="232,84,0,0" Name="label3" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="323,86,0,0" Name="textBox3" VerticalAlignment="Top" Width="120" Text="{Binding Path=User/Firstname}" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="368,159,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=UserSave}" />
</Grid>
</Window>
So basically, when I edit a surname, the Save button should be enabled; and if I undo my edit - well then it should be Disabled again as nothing has changed.
I have seen this in many examples, but have not yet found out how to do it.
Any help would be much appreciated!
Brendan
In my experience, if you implement IsDirty in your view model, you probably also want the view model to implement IEditableObject.
Assuming that your view model is the usual sort, implementing PropertyChanged and a private or protected OnPropertyChanged method that raises it, setting IsDirty is simple enough: you just set IsDirty in OnPropertyChanged if it isn't already true.
Your IsDirty setter should, if the property was false and is now true, call BeginEdit.
Your Save command should call EndEdit, which updates the data model and sets IsDirty to false.
Your Cancel command should call CancelEdit, which refreshes the view model from the data model and sets IsDirty to false.
The CanSave and CanCancel properties (assuming you're using a RelayCommand for these commands) just return the current value of IsDirty.
Note that since none of this functionality depends on the specific implementation of the view model, you can put it in an abstract base class. Derived classes don't have to implement any of the command-related properties or the IsDirty property; they just have to override BeginEdit, EndEdit, and CancelEdit.
I've done some work on implementing IsDirty for models that is wrapped in my ViewModel.
The result really simplified my ViewModels:
public class PersonViewModel : ViewModelBase
{
private readonly ModelDataStore<Person> data;
public PersonViewModel()
{
data = new ModelDataStore<Person>(new Person());
}
public PersonViewModel(Person person)
{
data = new ModelDataStore<Person>(person);
}
#region Properties
#region Name
public string Name
{
get { return data.Model.Name; }
set { data.SetPropertyAndRaisePropertyChanged("Name", value, this); }
}
#endregion
#region Age
public int Age
{
get { return data.Model.Age; }
set { data.SetPropertyAndRaisePropertyChanged("Age", value, this); }
}
#endregion
#endregion
}
Code # http://wpfcontrols.codeplex.com/
Check under the Patterns assembly and MVVM folder, you'll find a ModelDataStore class.
P.S.
I haven't done a full scale test on it, just the really simple test you'll find the Test assembly.
I would suggest you to use GalaSoft MVVM Light Toolkit as it is much more easier to implement than DIY approach.
For dirty reads, you need to keep the snapshot of each fields, and return true or false from UserSaveCanExecute() method, which will enable / disable command button accordingly.
If you wanted to take a framework approach rather than writing the infrastructure yourself, you could use CSLA (http://www.lhotka.net/cslanet/) - Rocky's framework for developing business objects. Object state is managed for you on property changes, and the code base also includes an example ViewModel type which supports an underlying model, a Save verb, and a CanSave property. You may be able to take inspiration from the code, even you didn't want to use the framework.
I have come up with a working solution. This may of course not be the best way, but I am sure I can work on it as I learn more...
When I run the project, if I cange any item, the list box is disabled, and the save button enabled. If I undo my edits, then the list box is enabled again, and the save button disabled.
I have changed my User Model to implement INotifyPropertyChanged, and I have also created a set of private variables to store the "original values" and some logic to check for "IsDirty"
using System.ComponentModel;
namespace Test.Model
{
public class User : INotifyPropertyChanged
{
//Private variables
private string _username;
private string _surname;
private string _firstname;
//Private - original holders
private string _username_Orig;
private string _surname_Orig;
private string _firstname_Orig;
private bool _isDirty;
//Properties
public string UserName
{
get
{
return _username;
}
set
{
if (_username_Orig == null)
{
_username_Orig = value;
}
_username = value;
SetDirty();
}
}
public string Surname
{
get { return _surname; }
set
{
if (_surname_Orig == null)
{
_surname_Orig = value;
}
_surname = value;
SetDirty();
}
}
public string Firstname
{
get { return _firstname; }
set
{
if (_firstname_Orig == null)
{
_firstname_Orig = value;
}
_firstname = value;
SetDirty();
}
}
public bool IsDirty
{
get
{
return _isDirty;
}
}
public void SetToClean()
{
_username_Orig = _username;
_surname_Orig = _surname;
_firstname_Orig = _firstname;
_isDirty = false;
OnPropertyChanged("IsDirty");
}
private void SetDirty()
{
if (_username == _username_Orig && _surname == _surname_Orig && _firstname == _firstname_Orig)
{
if (_isDirty)
{
_isDirty = false;
OnPropertyChanged("IsDirty");
}
}
else
{
if (!_isDirty)
{
_isDirty = true;
OnPropertyChanged("IsDirty");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, my ViewModel has changed a bit too....
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Test.Model;
using System.ComponentModel;
namespace Test.ViewModel
{
class UserViewModel : ViewModelBase
{
//Private variables
private ObservableCollection<User> _users;
RelayCommand _userSave;
private User _selectedUser = new User();
//Properties
public ObservableCollection<User> User
{
get
{
if (_users == null)
{
_users = new ObservableCollection<User>();
_users.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// handle property changing
foreach (User item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += (s1, e1) =>
{
OnPropertyChanged("EnableListBox");
};
}
}
};
//Populate with users
_users.Add(new User {UserName = "Bob", Firstname="Bob", Surname="Smith"});
_users.Add(new User {UserName = "Smob", Firstname="John", Surname="Davy"});
}
return _users;
}
}
public User SelectedUser
{
get { return _selectedUser; }
set { _selectedUser = value; }
}
public bool EnableListBox
{
get { return !_selectedUser.IsDirty; }
}
//Commands
public ICommand UserSave
{
get
{
if (_userSave == null)
{
_userSave = new RelayCommand(param => this.UserSaveExecute(), param => this.UserSaveCanExecute);
}
return _userSave;
}
}
void UserSaveExecute()
{
//Here I will call my DataAccess to actually save the data
//Save code...
_selectedUser.SetToClean();
OnPropertyChanged("EnableListBox");
}
bool UserSaveCanExecute
{
get
{
return _selectedUser.IsDirty;
}
}
//constructor
public UserViewModel()
{
}
}
Finally, the XAML
I changed the bindings on the Username, Surname & Firstname to include UpdateSourceTrigger=PropertyChanged
And then I bound the listbox's SelectedItem and IsEnabled
As I said in the beginning - it may not be the best solution, but it seems to work...
Since your UserSave command is in the ViewModel, I would do the tracking of the "dirty" state there. I would databind to the selected item in the ListBox, and when it changes, store a snapshot of the current values of the selected user's properties. Then you can compare to this to determine if the command should be enabled/disabled.
However, since you are binding directly to the model, you need some way to find out if something changed. Either you also implement INotifyPropertyChanged in the model, or wrap the properties in a ViewModel.
Note that when the CanExecute of the command changes, you may need to fire CommandManager.InvalidateRequerySuggested().
This is how I have implemented IsDirty. Create a wrapper for every property of User class (inheriting User class with IPropertyChanged and implementing onpropertychanged in User class wont help) in your ViewModal. You need to change your binding from UserName to WrapUserName.
public string WrapUserName
{
get
{
return User.UserName
}
set
{
User.UserName = value;
OnPropertyChanged("WrapUserName");
}
}
Now have a property
public bool isPageDirty
{
get;
set;
}
Since your viewmodal inherits from baseviewmodal and baseviewmodal implements onPropertyChanged.
UserViewModel.PropertyChanged += (s, e) => { isPageDirty = true; };
In case any of the propertychanges,isPageDirty will be true, So while saving you chan check isPageDirty.

Categories

Resources