Windows 8 C# to XAML databinding issues, GUI not updating - c#

I'm Trying to make a simple quiz program in Windows 8 using the MVVM design pattern. I tried to Use PRISM and MVVMlite but I'm a newb and simply don't have enough knowledge of data and control binding to understand how to use it correctly. I think I have the majority of it working but I a few major problems.
1. my GUI doesn't update properly.
2. I'm receiving several errors.
3. Fixing one part of my code breaks another part.
4. Can't figure out how to get "sender" information from command in XAML.
here is my code so far:
xml data:
<root>
<Object>
<Question>What do you do for work</Question>
<Answer>Wrestle giant tentical monsters</Answer>
<Choices>Battle robots</Choices>
<Choices>Glorious ruler of North Korea</Choices>
<Choices>Wrestle Giant Tentical Monsters</Choices>
<Choices>Defender of all that is good</Choices>
</Object>
<Object>
<Question>What do you drive</Question>
<Answer>Moped</Answer>
<Choices>Helicopter</Choices>
<Choices>Pegasus</Choices>
<Choices>Rocketship</Choices>
<Choices>Moped</Choices>
</Object>
</root>
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Xml.Linq;
using System.Windows.Input;
namespace Quiz
{
class QuizModel : INotifyPropertyChanged
{
private string _question;
public string Question
{
get { return _question; }
set
{
_question = value;
OnPropertyChanged("Question");
}
}
private string _answer;
public string Answer
{
get { return _answer; }
set
{
_answer = value;
OnPropertyChanged("Answer");
}
}
private List<string> _choices;
public List<string> Choices
{
get { return _choices; }
set
{
_choices = value;
OnPropertyChanged("Choices");
}
}
public QuizModel(string quesiton, string answer, List<string> choices)
{
Question = quesiton;
Answer = answer;
Choices = choices;
}
public static List<QuizModel> Query(string datasource)
{
XElement quizdata = XElement.Load(datasource);
List<QuizModel> query = (from d in quizdata.Descendants("Object")
select new QuizModel(
(string)d.Element("Question"),
(string)d.Element("Answer"),
d.Elements("Choices").Select(a => a.Value).ToList()
)).ToList();
return query;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View Model:
class QuizViewModel
{
public static List<QuizModel> QuizList { get; set; }
public static QuizModel Quiz { get; set; }
public static int Indexer { get; set; }
public ICommand myCommand { get; set; }
//Initiallizes view model
public QuizViewModel()
{
Indexer = 0;
QuizList = QuizModel.Query("Quiz.xml");
Quiz = QuizList[Indexer];
myCommand = new ActionCommand(Evaluate);
}
//Increments to next question
private void Evaluate()
{
Indexer++;
Quiz = QuizList[Indexer];
}
}
iCommand:
public class ActionCommand : ICommand
{
private readonly Action _action;
public ActionCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged; //ERROR event Never Used
}
}
View:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<TextBlock FontSize="50" Text="{Binding Quiz.Question}">
<TextBlock.DataContext>
<local:QuizViewModel/> <!--Can't find Quiz.xml-->
</TextBlock.DataContext>
</TextBlock>
<ListView Grid.Row="1" FontSize="30" ItemsSource="{Binding Quiz.Choices}">
<ListView.DataContext>
<local:QuizViewModel/> <!--Can't find Quiz.xml-->
</ListView.DataContext>
<ListView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Mode=OneWay}" Command="{Binding myCommand}">
<Button.DataContext>
<local:QuizViewModel/>
</Button.DataContext>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I have 3 current errors 2 of which are the same
first error refers to in the XAMLs datacontext:
Error 1 (x2)
Could not find file 'C:\Users\Me\AppData\Local\Microsoft\VisualStudio\11.0\Designer\ShadowCache\cv0te54x.fpv\5ncl4yxi.hui\Quiz.xml'.
Error 2
Cannot create instance of type 'Quiz.QuizViewModel'
This seems to effect my "Choices" not populating, I can fix this by removing the data context, but then I can't bind "myCommand"
Third problem is how do I the Sender information from the command input so I can evaluate if it is right or wrong?

Take a look at the error #1, your code can't find the Quiz.xml file and it is looking for it at the location in the error description. It seems it's looking at the wrong location, so you might have to specify a more concrete path for it. This question may help, if you have the xml file in the resources. Because this is done in the Quiz.QuizViewModel constructor, the creating of the instance fails and produces error #2.
As for the third part, you can pass the command an arbitrary parameter. In this case it might be the choice or its position. Something like this

Related

Xamarin.Forms List bound to ListView sometimes briefly displays elements, then everything disapears

I followed the documentation from microsoft to create a multipage app with a local database that shows short notes. However, this does not follow MVVM. So I attempted to restructure the code to adhere to the MVVM design.
In my App.xaml.cs file I create a NavigationPage to the MainPage, which is a ContentPage that contains the ContentView for the list of notes. I tested whether my notes get loaded properly, by writing them to the debugger, which is working properly. My NotesView.xaml is this:
...
<ContentView.BindingContext>
<viewModel:NotesViewModel />
</ContentView.BindingContext>
<ContentView.Content>
<StackLayout>
<ListView Margin="{StaticResource PageMargin}"
ItemsSource="{Binding Notes}"
SelectedItem="{Binding SelectedNote}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Text}"
Detail="{Binding Date}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentView.Content>
</ContentView>
My NotesViewModel:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;
using AwesomeApp.Models;
namespace AwesomeApp.ViewModels
{
class NotesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public NotesViewModel()
{
notes = new List<Note>();
_selectedNote = new Note();
LoadNotes();
}
private Note _selectedNote;
public Note SelectedNote
{
get
{
return _selectedNote;
}
set
{
if (_selectedNote != value)
{
_selectedNote = value;
}
}
}
List<Note> notes;
public List<Note> Notes
{
set
{
if (value != notes)
{
notes = value;
OnPropertyChanged("Notes");
}
}
get
{
return notes;
}
}
public async void LoadNotes()
{
notes = await App.Database.GetNotesAsync();
foreach (var note in Notes)
{
System.Diagnostics.Debug.WriteLine("Notes: {0},{1},{2}", note.ID, note.Text, note.Date);
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My model Notes contains three properties:
public class Note : INotifyPropertyChanged
{
private int id;
private string text;
private DateTime date;
...
When I now try to display a ListView with items from a List, the items very briefly appear and then disappear. This displaying of the elements only sometimes happen when I rebuild the app when I made layout changes and saved them so that HotReload triggers during debugging on an android 9.0 phone.
Without being able to run/debug your application I found a couple of mistakes which I think are creating these problems.
1- Change the constructor to:
public NotesViewModel()
{
LoadNotes();
}
If you want to select a note, do not instantiate a new one as _selectedNote = new Note();. The selected note must be pointing to a note on the Notes list.
2- In the LoadNotes() you have to do:
Notes = await App.Database.GetNotesAsync();
Keep in mind to always use the property and not the private field. Since you do notes = ..., the property is not triggering an OnPropertyChanged("Notes");. So you either have to do the:
notes = await App.Database.GetNotesAsync();
OnPropertyChanged("Notes");
or (better):
Notes = await App.Database.GetNotesAsync();
3- Your model class, should not implement the INotifyPropertyChanged interface and it should be directly something like:
public class Note {
private int id;
private string text;
private DateTime date;
}
In any case you have your ViewModel to do such kind of trickery and notify your View about a change in the ViewModel data.

Understanding MVVM and ObjectDetailViewModel in Udemy Course

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

Data bindings do not see viewmodel properties

Problem:
I am trying to create what seems to be a simple MVVM view setup. However, no matter what I modify, I can't seem to make the PropertyChanged hookup connect to the .xaml and vice-versa.
Here's the View:
VpicInformationPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:ScanditBarcodeScanner.ViewModels"
x:Class="ScanditBarcodeScanner.Pages.VehicleOperationPages.VpicInformationPage">
<!--<ContentPage.BindingContext>
<viewModels:VpicInformationPageViewModel />
</ContentPage.BindingContext>-->
<StackLayout VerticalOptions="CenterAndExpand" Padding="5">
<StackLayout.BindingContext>
<viewModels:VpicInformationPageViewModel />
</StackLayout.BindingContext>
<Entry x:Name="VinEntry" Placeholder="VIN (Manual Entry)" />
<Label Text="{Binding VinType.Make}" />
<Label Text="{Binding VinType.Model}" />
<Label Text="{Binding VinType.ModelYear}" />
<Label Text="{Binding VinType.BodyClass}" />
<Label Text="{Binding VinType.ErrorCode}" />
<Button Text="Scan/Check VIN" Clicked="ScanOrCheckVin_Clicked"/>
</StackLayout>
</ContentPage>
The Model:
VpicInformationPage.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Scandit.BarcodePicker.Unified;
using Scandit.BarcodePicker.Unified.Abstractions;
using ScanditBarcodeScanner.ViewModels;
using ScanditBarcodeScanner.ViewModels.Base;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ScanditBarcodeScanner.Pages.VehicleOperationPages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class VpicInformationPage : ContentPage, INotifyPropertyChanged
{
IBarcodePicker _picker;
VpicInformationPageViewModel ViewModel;
public VpicInformationPage()
{
InitializeComponent ();
ViewModel = BindingContext as VpicInformationPageViewModel;
ViewModel.VinType = VehicleApi.EmptyVinType;
_picker = ScanditService.BarcodePicker;
SetVinSettings();
_picker.DidScan += OnDidScan;
VinEntry.Text = "";
}
//...
}
}
The ViewModel:
VpicInformationPageViewModel.cs
using ScanditBarcodeScanner.ViewModels.Base;
using System.ComponentModel;
namespace ScanditBarcodeScanner.ViewModels
{
public class VpicInformationPageViewModel : ViewModelBase
{
#region VinType
private VehicleApi.VinType _vinType;
public VehicleApi.VinType VinType
{
get { return _vinType; }
set
{
SetValue(ref _vinType, value, "VinType");
Make = _vinType.Make;
Model = _vinType.Model;
ModelYear = _vinType.ModelYear;
BodyClass = _vinType.BodyClass;
ErrorCode = _vinType.ErrorCode;
}
}
#endregion VinType
#region VinType.Make
private string _make;
public string Make
{
get { return _vinType.Make; }
private set
{
SetValue(ref _make, value);
}
}
#endregion VinType.Make
#region VinType.Model
private string _model;
public string Model
{
get { return _vinType.Model; }
private set
{
SetValue(ref _model, value);
}
}
#endregion VinType.Model
#region VinType.ModelYear
private string _modelYear;
public string ModelYear
{
get { return _vinType.ModelYear; }
private set
{
SetValue(ref _modelYear, value);
}
}
#endregion VinType.ModelYear
#region VinType.BodyClass
private string _bodyClass;
public string BodyClass
{
get { return _vinType.BodyClass; }
private set
{
SetValue(ref _bodyClass, value);
}
}
#endregion VinType.BodyClass
#region VinType.ErrorCode
private string _errorCode;
public string ErrorCode
{
get { return _vinType.ErrorCode; }
private set
{
SetValue(ref _errorCode, value);
}
}
#endregion VinType.ErrorCode
public VpicInformationPageViewModel()
{
_vinType = new VehicleApi.VinType();
}
}
}
The ViewModelBase:
ViewModelBase.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ScanditBarcodeScanner.ViewModels.Base
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
protected bool SetValue<T>(ref T BackingField, T Value, [CallerMemberName] string PropertyName = null)
{
if(EqualityComparer<T>.Default.Equals(BackingField, Value))
{
return false;
}
BackingField = Value;
OnPropertyChanged(PropertyName);
return true;
}
}
I realized that I didn't include the VehicleApi class. Here is the important part of that class:
VehicleApi.cs
namespace ScanditBarcodeScanner
{
public static class VehicleApi
{
public static VinType EmptyVinType { get; } = new VinType
{
Make = "Make",
Model = "Model",
ModelYear = "Model Year",
BodyClass = "Body Class",
ErrorCode = "Status/Error Code"
};
public class VinType
{
public string Make { get; set; }
public string Model { get; set; }
public string ModelYear { get; set; }
public string BodyClass { get; set; }
public string ErrorCode { get; set; }
}
}
It is my understanding that I have implemented these files correctly and linked them together properly. However, every time I run the app, I get:
[0:] Binding: 'VinType' property not found on 'ScanditBarcodeScanner.ViewModels.VpicInformationPageViewModel', target property: 'Xamarin.Forms.Label.Text'
I have bound the View to the ViewModel, and implemented INotifyPropertyChanged.
I have tried many solutions, such as changing where I set the binding context (either in the ContentPage or in the StackLayout, or even both), trying different methods of notifying the view that properties have changed, and binding the labels to the underlying members of VinType, and allowing VinType to modify and raise PropertyChanged for them. I even tried PropertyChanged.Fody, but it seems the issue is not with the notifying code, but with the way I have bound the View and ViewModel together, or perhaps with how the properties are defined.
Question:
What part of the binding hookup is missing/incorrect? The documentation states that I should just be able to access VinType and its members with the code I have provided.
Link to sample
The issue with the project above, is that, while it doesn't give the same error I'm talking about, it still doesn't change the fields when it's supposed to.
So, the fix to this issue was actually something rather simple.
I just needed to change the label definitions from
<Label Text="{Binding PropertyName.Member}" /> to <Label Text="{Binding PropertyName.Member, StringFormat='{}{0}'}">
The reason there needs to be the {} in the format string is because of an error that comes up when you use single quotes '' instead of double quotes "" when defining StringFormat.
EDIT: While the StringFormat issue is important, the larger issue is that the properties in the objects I was using did not have get or set defined.
As long as a Type is something along the lines of this:
public class TypeName
{
public Type Property1 { get; set; }
public Type Property2 { get; set; }
}
Then the binding engine will be able to see and edit the member properties.

Trouble copying a Grid object from one TabItem to another

In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.
Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.
The class I am using:
public static class copyTabItems
{
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
}
When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".
What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?
Ok. This is how you're supposed to deal with a TabControl in WPF:
<Window x:Class="MiscSamples.MVVMTabControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MVVMTabControlSample" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Tab1ViewModel}">
<!-- Here I just put UI elements and DataBinding -->
<!-- You may want to encapsulate these into separate UserControls or something -->
<StackPanel>
<TextBlock Text="This is Tab1ViewModel!!"/>
<TextBlock Text="Text1:"/>
<TextBox Text="{Binding Text1}"/>
<TextBlock Text="Text2:"/>
<TextBox Text="{Binding Text2}"/>
<CheckBox IsChecked="{Binding MyBoolean}"/>
<Button Command="{Binding MyCommand}" Content="My Command!"/>
</StackPanel>
</DataTemplate>
<!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
</Window.Resources>
<DockPanel>
<Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
DockPanel.Dock="Bottom"/>
<TabControl ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}"
DisplayMemberPath="Title">
</TabControl>
</DockPanel>
</Window>
Code Behind:
public partial class MVVMTabControlSample : Window
{
public MVVMTabControlSample()
{
InitializeComponent();
DataContext = new MVVMTabControlViewModel();
}
}
Main ViewModel:
public class MVVMTabControlViewModel: PropertyChangedBase
{
public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }
private MVVMTabItemViewModel _selectedTab;
public MVVMTabItemViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public Command AddNewTabCommand { get; set; }
public MVVMTabControlViewModel()
{
Tabs = new ObservableCollection<MVVMTabItemViewModel>();
AddNewTabCommand = new Command(AddNewTab);
}
private void AddNewTab()
{
//Here I just create a new instance of TabViewModel
//If you want to copy the **Data** from a previous tab or something you need to
//copy the property values from the previously selected ViewModel or whatever.
var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
Tabs.Add(newtab);
SelectedTab = newtab;
}
}
Abstract TabItem ViewModel (you to derive from this to create each different Tab "Widget")
public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
public string Title { get; set; }
//Here you may want to add additional properties and logic common to ALL tab types.
}
TabItem 1 ViewModel:
public class Tab1ViewModel: MVVMTabItemViewModel
{
private string _text1;
private string _text2;
private bool _myBoolean;
public Tab1ViewModel()
{
MyCommand = new Command(MyMethod);
}
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
OnPropertyChanged("Text1");
}
}
public bool MyBoolean
{
get { return _myBoolean; }
set
{
_myBoolean = value;
MyCommand.IsEnabled = !value;
}
}
public string Text2
{
get { return _text2; }
set
{
_text2 = value;
OnPropertyChanged("Text2");
}
}
public Command MyCommand { get; set; }
private void MyMethod()
{
Text1 = Text2;
}
}
Edit: I forgot to post the Command class (though you surely have your own)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
And finally PropertyChangedBase (just a helper class)
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Result:
Basically, each Tab Item type is a Widget, which contains its own logic and Data.
You define all logic and data at the ViewModel or Model level, and never at the UI level.
You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

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