Am I following the MVVM pattern correctly? [closed] - c#

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
Did I break the rules of MVVM if ever have that rule?
Did I connect my database correctly?
My XML
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewBox="clr-namespace:WpfApplication7.ViewModel"
xmlns:vm="clr-namespace:WpfApplication7.ViewModel.Command"
xmlns:local="clr-namespace:WpfApplication7"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewBox:viewModel x:Key="testView"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource testView}}">
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Margin="175,47,0,0" Text="{Binding fname, Source={StaticResource testView}}"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="0,95,0,0" Command="{Binding test, Source={StaticResource testView}}"/>
<TextBox x:Name="textBox_Copy" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Margin="175,81,0,0" Text="{Binding mname, Source={StaticResource testView}}"/>
<TextBox x:Name="textBox_Copy1" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Margin="175,109,0,0" Text="{Binding lname, Source={StaticResource testView}}"/>
<TextBox x:Name="textBox_Copy2" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Margin="175,175,0,0" Text="{Binding IDNum}"/>
</Grid>
</Window>
This is my ViewModel Class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApplication7.ViewModel.Command;
using WpfApplication7.Model;
namespace WpfApplication7.ViewModel
{
public class viewModel : INotifyPropertyChanged
{
private databaseTest obj = new databaseTest();
public btnTest test { get; set; }
public viewModel()
{
test = new btnTest(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public string fname
{
get { return obj.firstname; }
set { obj.firstname = value;
OnPropertyChanged("fname");
}
}
public string mname
{
get { return obj.middlename; }
set { obj.middlename = value;
OnPropertyChanged("mname");
}
}
public string lname
{
get { return obj.lastname; }
set { obj.lastname = value;
OnPropertyChanged("lname");
}
}
public int IDNum
{
get { return obj.idNum; }
set { obj.idNum = value;
OnPropertyChanged("IDNum");
}
}
public void searchBtn()
{
obj.sql = #"Provider = Microsoft.ACE.OLEDB.12.0; data source = C:\Users\veekat\Documents\VeeKat.mdb";
obj.conn = new System.Data.OleDb.OleDbConnection(obj.sql);
obj.cmd = new System.Data.OleDb.OleDbCommand("select * from info where ID = "+ this.IDNum +"", obj.conn);
try
{
obj.conn.Open();
obj.dr = obj.cmd.ExecuteReader();
if (obj.dr.Read())
{
this.fname = obj.dr["Firstname"].ToString();
this.mname = obj.dr["Middlename"].ToString();
this.lname = obj.dr["Lastname"].ToString();
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
This is my Command class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApplication7.ViewModel.Command
{
public class btnTest : ICommand
{
public viewModel viewM { get; set; }
public btnTest(viewModel vw)
{
this.viewM = vw;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
viewM.searchBtn();
}
}
}
And here my Model Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.OleDb;
namespace WpfApplication7.Model
{
public class databaseTest
{
private string Firstname;
private string Middlename;
private string Lastname;
public string firstname
{
get { return Firstname; }
set { Firstname = value; }
}
public string middlename
{
get { return Middlename; }
set { Middlename = value; }
}
public string lastname
{
get { return Lastname; }
set { Lastname = value; }
}
private int num;
public int idNum
{
get { return num; }
set { num = value; }
}
public string sql { get; set; }
public OleDbConnection conn { get; set; }
public OleDbCommand cmd { get; set; }
public OleDbDataReader dr { get; set; }
}
}
So far I don't have any errors, but I want to know your opinion.
as you can see, i search it using the id number

The MVVM pattern you followed is not incorrect. But it could be improved.
In my opinion the best thing to do is to separate the database related stuff to a new class file.
Add a new class file DBManager and add all the database related CRUD operations inside the class file. Make this class a singleton class. It's upto you. The use of separating the db operations is that when your project becomes very large, it is easy to manipulate data when it is in a single file, rather than dealing it in every model.

Is your question "is it good practise to place my database IO inside my MVVM ViewModel ?" In which case a good discussion of this can be found in this MVVM where to put Data Access Layer? "MVVM where to put Data Access Layer?" which should point you in the right direction.

Related

Winui3 Desktop; ObservableCollection, updating UI when property changes? Updating from different thread

This is a small test app to try and figure this problem out from my main app. I'll paste the code first.
XAML:
<Window
x:Class="ThreadTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThreadTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="Black" BorderThickness="2" Grid.Row="0"/>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0">
<Button Click="{x:Bind ViewModel.AddPosition}" Content="Add To List" Margin="5"/>
<TextBlock Text="{x:Bind ViewModel.OutputString, Mode=OneWay}" Margin="5"/>
<ListView ItemsSource="{x:Bind ViewModel.PositionCollection, Mode=OneWay}" Margin="5">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="ID" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
<Border Grid.Column="1" BorderBrush="BlueViolet" BorderThickness="0,0,0,1">
<TextBlock Text="Place" Margin="5,0,0,0" FontWeight="Bold"/>
</Border>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PositionModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path=ID, Mode=OneWay}"/>
<TextBlock Grid.Column="1" Text="{x:Bind Path=Place, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Window>
ViewModel (MainViewModel.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.UI.Dispatching;
using Windows.UI.Core;
using Windows.ApplicationModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ThreadTest {
public class MainViewModel : BindableBase, INotifyPropertyChanged {
private String outputString = "Empty";
public MainViewModel() {
}
public String OutputString {
get { return outputString; }
set { SetProperty(ref outputString, value); }
}
private Random _random = new Random();
private int _id = 0;
private ObservableCollection<PositionModel> _positioncollection = new();
public ObservableCollection<PositionModel> PositionCollection {
get { return _positioncollection; }
set { SetProperty(ref _positioncollection, value); }
}
public async void AddPosition() {
Progress<PositionModel> progress = new();
progress.ProgressChanged += Progress_ProgressChanged;
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClass positionsClass = new(ref _position, progress);
await Task.Run(() => { positionsClass.Start(); });
}
private void Progress_ProgressChanged(object sender, PositionModel e) {
// This is so I can see that the thread is actually running.
OutputString = Convert.ToString(e.Place);
}
}
}
BindableBase.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class BindableBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
}
}
PositionModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionModel {
/*
//Impliment INotifyPropertyChanged up above if using this.
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T originalValue, T newValue, [CallerMemberName] string propertyName = null) {
if (Equals(originalValue, newValue)) {
return false;
}
originalValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
private int _id = 0;
public int ID {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private int _place = 0;
public int Place {
get { return _place; }
set { SetProperty(ref _place, value); }
}
*/
public int ID { get; set; }
public int Place { get; set; }
}
}
PositionsClass.cs:
using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest {
public class PositionsClass {
private IProgress<PositionModel> _progress;
private PositionModel _position;
public PositionsClass(ref PositionModel position, IProgress<PositionModel> progress) {
_progress = progress;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_position.Place++;
// Send position back.
_progress.Report(_position);
Thread.Sleep(100);
}
}
}
}
So basically you'll click the 'add to list' button which will then create the PositionsClass positionsClass, create and populate the PositionModel _position, create an ObservableCollection PositionCollection (bound to listview on xaml) then spin off the class into it's own thread. The class will get _position and increase its .Place, then progress.report the _position back to main thread.
Now I'm trying to figure out how to get the PositionCollection (of ObservableCollection) to update the lisview ui. I subscribed to progress.ProgressChanged and update the OutputString just to make sure the class is actually running and incrementing which does work.
I've tried various things I've found on the web, including different inherited ObversableCollection methods, none of which work or I missunderstood them.
I thought implementing an INotifyPropertyChange on PositionModel.cs itself would work (the commented out code), but doing so brings up a cross thread error. I imagine it's because positionsClass on a seperate thread is updating .Place which is causing the cross thread error?
Can anyone help explain how to get the ObservableCollection to update the ui when it's property changes in my example above? Thanks! In my main app, I'll be updating a lot of properties on a separate thread, rather than just the 2 in this example. Which is why I thought it'd be easier to just send the whole model back in a progress.report.
I think I've figured it out. First I enable the INotifyProperty on PositionModel.cs code above (the commented out part). Then I add:
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public CoreDispatcher Dispatcher { get; }
To the MainViewModel, and modify AddPosition to:
public async void AddPositionDispatcher() {
// Increase id for each new position added.
_id++;
// Setup/
var _position = new PositionModel {
ID = _id,
Place = _random.Next(1, 1000), // Get a random starting point.
};
PositionCollection.Add(_position);
PositionsClassDispatcher positionsClassDispatcher = new(_position, _dispatcherQueue);
await Task.Run(() => { positionsClassDispatcher.Start(); });
}
Where I send a DispatcherQueue to the new modified PositionsClassDispatcher.cs:
using Microsoft.UI.Dispatching;
using System.Threading;
namespace ThreadTest {
internal class PositionsClassDispatcher {
private PositionModel _position;
DispatcherQueue _queue;
public PositionsClassDispatcher(PositionModel position, DispatcherQueue dispatcherQueue) {
_queue = dispatcherQueue;
_position = position;
}
public void Start() {
StartFakePosition();
}
private void StartFakePosition() {
// Just using a quick loop to keep the numbers going up.
while (true) {
_queue.TryEnqueue(() => {
_position.Place++;
});
Thread.Sleep(100);
}
}
}
}
Which will take the DispatcherQueue and use TryEnqueue to update _position.Place. ObvservableCollection now properly updates UI when properties are updated. Also update the XAML to use the new AddPositionDispatcher().
Also, having to use DispatcherQueue rather than Dispatcher as WinUI3 seems to not have have Dispatcher anymore.
Window.Dispatcher Property
Window.Dispatcher may be altered or unavailable in future releases. Use Window.DispatcherQueue instead.
Which has caused quite a few problems trying to figure this problem out, as a lot of info out there is based on Dispatcher rather than DispatcherQueue.
Hope this helps anyone else that runs into the problem.

Could not create an instance of type ViewModel

I'm new to MVVM and WPF.
I have created a simple addition application which gets two number as input and add the given number in database and provide the result in textbox.
The application works fine.
But the windows.xaml throws the following error
Could not create an instance of type ViewModel
Windows.xaml
<Window x:Class="addition.Window1"
xmlns:vm="clr-namespace:addition.ViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.DataContext>
<vm:ViewModel/>
</Window.DataContext>
<Grid>
<Label Height="28" Margin="28,54,0,0" Name="Number1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="48">Number</Label>
<TextBox Height="28" Margin="112,56,46,0" Text ="{Binding Path = FirstArgument}" Name="textBox1" VerticalAlignment="Top" />
<Label Margin="28,106,0,128" Name="Number2" Width="58" HorizontalAlignment="Left">Number1</Label>
<TextBox Height="28" Margin="112,120,46,120" Text ="{Binding Path = secondargument}" Name="textBox2" />
<Label Height="28" Margin="28,0,0,75" Name="label1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="58">Number2</Label>
<TextBox Height="23" Margin="112,0,46,68" Name="textBox3" Text="{Binding Path = Addedargument}" VerticalAlignment="Bottom" />
<Button Height="23" HorizontalAlignment="Left" Margin="39,0,0,16" Name="button1" VerticalAlignment="Bottom" Width="75" Command="{Binding AddNew}">Button</Button>
</Grid>
</Window>
The error occurs when i instantiate the database connection class in the viewmodel. The issue gets resolved when i comment out the databaseconnetion class.
ViewModel.cs:
using addition.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Design;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Data.SqlClient;
using System.Data;
using System.Configuration;
using System.ComponentModel;
namespace addition.ViewModel
{
class ViewModel : INotifyPropertyChanged
{
private Number n1 = new Number();
int num, num1;
//The issue is resolved when i comment the below instantiation
databaseconnection d1 = new databaseconnection();
public RelayCommand AddNew { get; set; }
private string _number1;
public string FirstArgument
{
get { return this._number1; }
set
{
this._number1 = value;
if (int.TryParse(_number1.ToString(), out num))
{
this.n1.number1 = num;
this.OnPropertyChanged("FirstArgument");
}
else { MessageBox.Show("The given Value is not a Number "); }
}
}
private string _number2;
public string secondargument
{
get { return this._number2; }
set
{
this._number2 = value;
if (int.TryParse(_number2.ToString(), out num1))
{
this.n1.number2 = num1;
this.OnPropertyChanged("secondargument");
}
else { MessageBox.Show("The given Value is not a Number "); }
}
}
private string _number3;
public string Addedargument
{
get { return this._number3; }
set
{
this._number3 = value;
this.OnPropertyChanged("Addedargument");
}
}
public ViewModel()
{
AddNew = new RelayCommand(o => AddNumbers());
}
public void AddNumbers()
{
d1.data(this);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
class databaseconnection
{
static string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MYConnectionString"].ConnectionString;
public void data(ViewModel m1)
{
string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MYConnectionString"].ConnectionString;
//The query is prone to sql injection
string sql = "SELECT " + "( cast( " + m1.FirstArgument +" as int) + " + "cast( " + m1.secondargument + " as int) )" + " as Addedargument";
// MessageBox.Show(sql);
DataSet ds = new DataSet();
using (var connection = new SqlConnection(connectionString))
{
using (var command = new SqlCommand(sql, connection))
{
SqlDataAdapter dataadapter = new SqlDataAdapter(command);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
m1.Addedargument = reader["Addedargument"].ToString();
}
}
}
}
}
}
}
Question:
since I'm new to wpf via mvvm I'm not sure whether database
connection should happen in ViewModel or in Model. Please let me know
whether my approach is correct.
Why I'm getting the error when i
instantiate the class even though the program and logic works fine in
runtime. The issue is I can't able to design the application due to
the error. The error occurs in
<Window.DataContext>
<vm:ViewModel/>
</Window.DataContext>
I think that you should comment the static field static string connectionString
in the class databaseconnection or initialize that field in the class constructor.

Combox SelectedItem does not apply when restoring from serialized ViewModel

I'm facing a strange problem when using C# WPF and MVVM Pattern while restoring a ViewModel (serialized using Json.Net).
The idea of the software is - when closing the window - to persist the current Viewmodel state in a json file.
At the next startup the app just serarches for the json.
If there a file, then deserialize it and restore the ViewModel (set public properties).
If there is no file, then the viewmodel is created and default values are set.
Now my problem is, that when restoring it with the json file, a combobox containing a list of a custom type, the combobox has values but no SelectedItem. When creating the viewmodel instance and initiailizing the public properties with default values (doing this via the code behind) then everything is fine.
Here is some code that represents the "error":
View
<Window x:Class="CrazyWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CrazyWpf"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Closing="Window_Closing"
Loaded="Window_Loaded">
<StackPanel
x:Name="rootElement"
Orientation="Vertical"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10">
<StackPanel.DataContext>
<local:DemoViewModel />
</StackPanel.DataContext>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblID"
Width="30"
Content="ID:"/>
<TextBox
x:Name="tbID"
Width="50"
Margin="30,0,0,0"
Text="{Binding ID, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblName"
Width="45"
Content="Name:"/>
<TextBox
x:Name="tbName"
Width="200"
Margin="15,0,0,0"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel
Orientation="Horizontal">
<Label
x:Name="lblStai"
Width="60"
Content="Status:"/>
<ComboBox
x:Name="cbStati"
Width="200"
ItemsSource="{Binding StatusTypeList}"
SelectedItem="{Binding StatusType, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"/>
</StackPanel>
</StackPanel>
</Window>
Code Behind
using System;
using System.Windows;
using System.IO;
using Newtonsoft.Json;
namespace CrazyWpf
{
public partial class MainWindow : Window
{
private DemoViewModel dvm;
public MainWindow()
{
InitializeComponent();
this.dvm = (DemoViewModel)this.rootElement.DataContext;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
if (File.Exists(filePath))
File.Delete(filePath);
File.WriteAllText(filePath, JsonConvert.SerializeObject(this.dvm, Formatting.Indented));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
if (!File.Exists(filePath))
{ this.SetDefaultSettings(); return; }
DemoViewModel d = JsonConvert.DeserializeObject<DemoViewModel>(File.ReadAllText(filePath));
this.dvm.ID = d.ID;
this.dvm.Name = d.Name;
this.dvm.StatusType = d.StatusType;
}
}
}
BaseViewModel:
using System.ComponentModel;
namespace CrazyWpf
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace CrazyWpf
{
class DemoViewModel : BaseViewModel
{
[JsonIgnore]
private int id;
[JsonProperty(Order = 1)]
public int ID
{
get { return this.id; }
set
{
if (this.id != value)
{
this.id = value;
this.OnPropertyChanged("ID");
}
}
}
[JsonIgnore]
private string name;
[JsonProperty(Order = 2)]
public string Name
{
get { return this.name; }
set
{
if (this.name != value && value != null)
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
}
[JsonIgnore]
private StatusTyp statusType;
[JsonProperty(Order = 3)]
public StatusTyp StatusType
{
get { return this.statusType; }
set
{
if (this.statusType != value && value != null)
{
this.statusType = value;
this.OnPropertyChanged("StatusType");
}
}
}
[JsonIgnore]
private List<StatusTyp> statusTypeList;
[JsonProperty(Order = 4)]
public List<StatusTyp> StatusTypeList
{
get { return this.statusTypeList; }
set
{
if (this.statusTypeList != value && value != null)
{
this.statusTypeList = value;
this.OnPropertyChanged("StatusTypeList");
}
}
}
public DemoViewModel()
{
this.StatusTypeList = new Func<List<StatusTyp>>(() =>
{
var list = Enum.GetValues(typeof(Status))
.Cast<Status>()
.ToDictionary(k => (int)k, v => v.ToString())
.Select(e => new StatusTyp()
{
Value = e.Key,
Name = e.Value,
Status =
Enum.GetValues(typeof(Status))
.Cast<Status>().
Where(x =>
{
return (int)x == e.Key;
}).FirstOrDefault()
})
.ToList();
return list;
})();
}
}
public class StatusTyp
{
public int Value { get; set; }
public string Name { get; set; }
public Status Status { get; set; }
}
public enum Status
{
NotDetermined = 0,
Determined = 1,
Undeterminded = 2,
Unknown = 3
}
}
If you have an ItemsSource and a SelectedItem, the instance in SelectedItem MUST BE in the collection bound to ItemsSource. If it is not, then your bindings will not work as expected.
The control uses reference equality to determine which item in ItemsSource is the one in SelectedItem and update the UI. This normally isn't a problem as the control populates SelectedItem for you, but if you are updating from the ViewModel side, you have to make sure your references are managed correctly.
This can be an issue when serializing/deserializing your view model. Most common serializers don't track references, and so cannot restore these on deserialization. The same object may be referenced multiple places in the original object graph, but after deserialization you now have multiple instances of the original spread throughout the rehydrated graph. This won't work with your requirements.
What you have to do is, after deserializing, find the matching instance in your collection and substitute it for the instance in SelectedItem. Or, use a serializer that tracks instances.. The XAML serializer already does this, and is a surprisingly good xml serializer for .net object graphs.

Binding data to ListBox in xaml getting data from webservice

I have this code:
using System;
using System.Collections.Generic;
using System.linq;
using System.Text;
using System.Threading.Tasks;
using System.Component-model;
namespace MEO.MODELS
{
public class Claims :INotifyPropertyChanged
{
public Claims()
{
}
private string description;
private string expenseHeaderId;
private string assginedTo;
private bool submitted;
private bool approved;
private bool authorised;
private DateTime updatedDate;
private DateTime createdDate;
private DateTime claimDate;
private DateTime lastModifiedDate;
private string expenseFormType;
public bool Approved
{
get
{
return this.approved;
}
set
{
if (value != this.approved)
{
this.approved = value;
this.NotfiyProperty("Approved");
}
}
}
public string AssignedTo
{
get
{
return this.assginedTo;
}
set
{
if (value != this.assginedTo)
{
this.assginedTo = value;
this.NotfiyProperty("AssignedTo");
}
}
}
public bool Authorised
{
get
{
return this.authorised;
}
set
{
if (value != authorised)
{
this.authorised = value;
this.NotfiyProperty("Authorised");
}
}
}
public bool Submitted
{
get
{
return this.submitted;
}
set
{
if (value != submitted)
{
this.submitted = value;
this.NotfiyProperty("Submitted");
}
}
}
public DateTime ClaimDate
{
get
{
return this.claimDate;
}
set
{
if (value != claimDate)
{
this.claimDate = value;
this.NotfiyProperty("ClaimDate");
}
}
}
public DateTime CreatedDate
{
get
{
return this.createdDate;
}
set
{
if (value != createdDate)
{
this.createdDate = value;
this.NotfiyProperty("CreatedDate");
}
}
}
public DateTime LastModifiedDate
{
get
{
return this.lastModifiedDate;
}
set
{
if (value != lastModifiedDate)
{
this.lastModifiedDate = value;
this.NotfiyProperty("LastModifiedDate");
}
}
}
public DateTime UpdatedDate
{
get
{
return this.updatedDate;
}
set
{
if (value != updatedDate)
{
this.updatedDate = value;
this.NotfiyProperty("UpdatedDate");
}
}
}
public string Description
{
get
{
return this.description;
}
set
{
if (value != this.description)
{
this.description = value;
this.NotfiyProperty("Description");
}
}
}
public string ExpenseFormType
{
get
{
return this.expenseFormType;
}
set
{
if (value != this.expenseFormType)
{
this.expenseFormType = value;
this.NotfiyProperty("ExpenseFormType");
}
}
}
public string ExpenseHeaderId
{
get
{
return this.expenseHeaderId;
}
set
{
if (value != this.expenseHeaderId)
{
this.expenseHeaderId = value;
this.NotfiyProperty("ExpenseHeaderId");
}
}
}
private void NotfiyProperty(string propertyName)
{
PropertyChangedEventHandler propertyChangedEventHandler = this.PropertyChanged;
if (propertyChangedEventHandler != null)
{
propertyChangedEventHandler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
And the following is the XAML which contains a Listbox.I have to bind the data getting from webservice calling a method called GetFullClaimLinesAsync by passing parameters when page is loaded.
<phone:PhoneApplicationPage
x:Class="MEO.Views.Result"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="12,17,0,28" Grid.ColumnSpan="2">
<Button Content="My Expenses On line" Background="#00ccff" Grid.Row="5" FontWeight="Bold" Name="head" Margin="-23,0,-13,-98" Foreground="White" BorderThickness="0"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Margin="0,35,24,10" Grid.ColumnSpan="2"/>
<!--<Button Content="Get Full Claims" Background="#00ccff" FontWeight="Bold" x:Name="claim" Click="claim_Click" Margin="67,105,97,-203" Foreground="White" BorderThickness="0" Grid.Row="1" RenderTransformOrigin="0.676,0.469"/>-->
<Grid Margin="0,203,10,-713" Grid.Row="1" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<ListBox HorizontalAlignment="Left" Name="listbox1" ItemsSource="{Binding}" VerticalAlignment="Top" Height="500" Width="0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50">
</ColumnDefinition>
<ColumnDefinition Width="50">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Description}" Margin="=3" Grid.Column="0"></TextBlock>
<TextBlock Text="{Binding ExpenseHeaderId}" Margin="=3" Grid.Column="1"></TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
and the following is the code behind file (Result.xaml.cs):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Xml.Linq;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using System.IO;
using System.Net.NetworkInformation;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using MEO.MODELS;
namespace MEO.Views
{
public partial class Result : PhoneApplicationPage
{
//public Guid rid = new Guid(NavigationContext.QueryString["id"]);
//public string rpswd = NavigationContext.QueryString["password"];
//public DateTime headerFromDate = DateTime.Today;
//public DateTime detaiFromDate = DateTime.Today;
ObservableCollection<Claims> oc = new ObservableCollection<Claims>();
public Result()
{
InitializeComponent();
Guid rid = new Guid("0525466131154515");
string rpswd = "hchdj455mjchdjkch7dc1njj";
DateTime headerFromDate = Convert.ToDateTime("555525545");
DateTime detaiFromDate = Convert.ToDateTime("38635");
LoginService.DXDataMobileSoapClient client = new LoginService.DXDataMobileSoapClient();
client.GetFullClaimLinesCompleted+=client_GetFullClaimLinesCompleted;
client.GetFullClaimLinesAsync(rid, rpswd, "", headerFromDate, detaiFromDate, rid);
}
private void client_GetFullClaimLinesCompleted(object sender, LoginService.GetFullClaimLinesCompletedEventArgs e)
{
try
{
if (e.Error == null)
{
XElement[] array = Enumerable.ToArray<XElement>(e.Result.ReturnedDataTable.Any1.Descendants("ClaimHeadersDT"));
if (Enumerable.Count<XElement>(array) > 0)
{
//ObservableCollection<Claims> oc = new ObservableCollection<Claims>();
for (int i = 0; i < Enumerable.Count<XElement>(array); i++)
{
oc.Add(new Claims()
{
Description = (string)array[i].Element("h_description"),
ExpenseHeaderId =(string)array[i].Element("h_expense_headerID"),
});
}
listbox1.ItemsSource = oc;
}
}
}
catch(Exception ex)
{
throw ex;
}
}
}
}
My problem is that I am getting data from web service i.e. from XML and the data is added to my collection i.e. ObservableCollection oc. But I am unable to bind the oc data to list box. I am getting error in App.xaml like unhandeled exception. The error is not catched in my catch block. However Listbox.Itemssource has data, containing 603 items.
i think you get this problem because the listbox use a Model as Datatemplate, what you getting from the webserver is xml data and turning to a string, i did something like that before, and goas like that...
private void client_GetFullClaimLinesCompleted(class.......)
{
oc.ItemsSource = e.Result;
}
as you said, you're getting 603 results but they can't be recognized on DataTemplate Model

WPF C# MVVM program not updating ListView

I am working on WPF, MVVM C# simple app for learning.
I do have my front-end having Table kind of structure using element ""
See "VehicalForm.xaml" below.
Below is code of my View as well as View-Model part. (I have given just necessary files. Please let me know if you need any other files)
App.xaml.cs
using Seris.ViewModels;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
namespace Seris
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public void OnStartup(object sender, StartupEventArgs e)
{
VehicalForm vehicalForm = new VehicalForm();
vehicalForm.DataContext = new VehicalMainViewModel();
vehicalForm.Show();
}
}
}
VehicalForm.xaml
<Window x:Class="Seris.VehicalForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<WrapPanel Orientation="Vertical" Margin="10 " >
<Label Content="Vehical No" HorizontalAlignment="Left"/>
<TextBox Name="VehicalNo_Text" Height="23" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left"/>
<Label Content="Model" HorizontalAlignment="Left"/>
<TextBox Name="Model_Text" Height="23" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left" />
<Label Content="Manufacturing Date" HorizontalAlignment="Left"/>
<DatePicker/>
<Label Content="IU No" HorizontalAlignment="Left"/>
<TextBox Height="23" Name="IUNO_Text" TextWrapping="Wrap" Text="TextBox" HorizontalAlignment="Left"/>
<Label Content="Personnel" HorizontalAlignment="Left"/>
<ComboBox Name="Personnel_Combo" HorizontalAlignment="Left" Width="116"/>
<Separator Height="20" RenderTransformOrigin="0.5,0.5" Width="16"/>
<Button Name="Save_Button" Command="{Binding SaveToList}" Content="Save" Width="66"/>
<ListView Height="294" Width="371" >
<ListView Height="294" Width="371" ItemsSource="{Binding listItems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Vehical No" DisplayMemberBinding="{Binding VehicalNo}" />
<GridViewColumn Header="Model" DisplayMemberBinding="{Binding Model}" />
<GridViewColumn Header="ManufacturingDate" DisplayMemberBinding="{Binding ManufacturingDate}" />
<GridViewColumn Header="IUNo" DisplayMemberBinding="{Binding IUNo}" />
<GridViewColumn Header="Personnel" DisplayMemberBinding="{Binding Personnel}" />
</GridView>
</ListView.View>
</ListView>
</WrapPanel>
VehicalForm.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Seris
{
public partial class VehicalForm : Window
{
public VehicalForm()
{
InitializeComponent();
}
}
}
VehicalMainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Seris.Models;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Seris.Commands;
using Seris.ViewModels;
namespace Seris.ViewModels
{
public class VehicalMainViewModel : ObservableObject
{
ObservableCollection<VehicalModel> listItems = new ObservableCollection<VehicalModel>();
#region Getter-Setter
private string _VehicalNo;
public string VehicalNo
{
get { return _VehicalNo; }
set
{
if (value != _VehicalNo)
{
_VehicalNo = value.Trim();
if(OnPropertyChanged("VehicalNo"))
listItems.Add(new VehicalModel(VehicalNo, Model, ManufacturingDate, IUNo, PersonnelName));
}
}
}
private string _Model;
public string Model
{
get { return _Model; }
set
{
if (value != _Model)
{
_Model = value.Trim();
OnPropertyChanged("Model");
}
}
}
private DateTime _ManufacturingDate;
public DateTime ManufacturingDate
{
get { return _ManufacturingDate; }
set
{
if (value != _ManufacturingDate)
{
_ManufacturingDate = value;
OnPropertyChanged("ManufacturingDate");
}
}
}
private string _IUNo;
public string IUNo
{
get { return _IUNo; }
set
{
if (value != _IUNo)
{
_IUNo = value.Trim();
OnPropertyChanged("IUNo");
}
}
}
private string _PersonnelName;
public string PersonnelName
{
get { return _PersonnelName; }
set
{
if (value != _PersonnelName)
{
_PersonnelName = value.Trim();
OnPropertyChanged("PersonnelName");
}
}
}
#endregion
private ICommand _saveButton_Command;
public ICommand SaveButton_Command
{
get { return _saveButton_Command; }
set { _saveButton_Command = value; }
}
public void SaveToList(object o1)
{
listItems.Add(new VehicalModel(VehicalNo,Model,ManufacturingDate,IUNo,PersonnelName));
}
public void RemoveFromList()
{
}
public VehicalMainViewModel()
{
VehicalModel vm=new VehicalModel();
SaveButton_Command = new RelayCommand(new Action<object>(SaveToList));
}
}
}
ObservableObject.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
namespace Seris.Models
{
public abstract class ObservableObject: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = PropertyChanged;
if(handler!=null)
{
if (propertyName != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
return true;
}
}
return false;
}
public void VerifyPropertyName(string propertyName)
{
if(TypeDescriptor.GetProperties(this)[propertyName]==null)
{
string msg = "Invalid Property Name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
public bool ThrowOnInvalidPropertyName { get; set; }
}
}
VehicalModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Seris.Models
{
public class VehicalModel : ObservableObject
{
#region Getter-Setter
private string _VehicalNo;
public string VehicalNo
{
get { return _VehicalNo; }
set
{
if (value != _VehicalNo)
{
_VehicalNo = value.Trim();
OnPropertyChanged("VehicalNo");
}
}
}
private string _Model;
public string Model
{
get { return _Model; }
set
{
if (value != _Model)
{
_Model = value.Trim();
OnPropertyChanged("Model");
}
}
}
private DateTime _ManufacturingDate;
public DateTime ManufacturingDate
{
get { return _ManufacturingDate; }
set
{
if (value != _ManufacturingDate)
{
_ManufacturingDate = value;
OnPropertyChanged("ManufacturingDate");
}
}
}
private string _IUNo;
public string IUNo
{
get { return _IUNo; }
set
{
if (value != _IUNo)
{
_IUNo = value.Trim();
OnPropertyChanged("IUNo");
}
}
}
private string _PersonnelName;
public string PersonnelName
{
get { return _PersonnelName; }
set
{
if (value != _PersonnelName)
{
_PersonnelName = value.Trim();
OnPropertyChanged("PersonnelName");
}
}
}
#endregion
#region Constructor
public VehicalModel(string VehicalNo, string Model, DateTime ManufacturingDate, string IUNo, string PersonnelName)
{
this.VehicalNo = VehicalNo;
this.Model = Model;
this.ManufacturingDate = ManufacturingDate;
this.IUNo = IUNo;
this.PersonnelName = PersonnelName;
}
public VehicalModel()
{
this.VehicalNo = null;
this.Model = null;
this.ManufacturingDate = DateTime.Now;
this.IUNo = null;
this.PersonnelName = null;
}
#endregion
#region Methods
#region Validate Methods
public bool Validate_VehicalNo()
{
if (matchRE(VehicalNo,"[A-Zz-z][A-Zz-z0-9]{6}"))
return true;
else
return false;
}
public bool Validate_Model()
{
if(Model!=null)
return true;
else
return false;
}
public bool Validate_ManufacturingDate()
{
return true;
}
public bool Validate_IUNo()
{
if(matchRE(IUNo,"[0-9]{10}"))
return true;
else
return false;
}
public bool Validate_PersonnelName()
{
if(matchRE(PersonnelName,"[A-Za-z]+"))
return true;
else
return false;
}
public bool matchRE(string stringToMatch, string regularExpression)
{
Regex regex = new Regex(#regularExpression);
Match match = regex.Match(stringToMatch);
if(match.Success)
return(true);
else
return(false);
}
#endregion
#endregion
}
}
What I need is
1) When I update VehicalNo, new row should be added in the table.
2) If I need to update individual elements of every row in future which should reflect in table as soon as I update , is there inbuilt facility in ListView? Or I need to use List for individual elements (i.e. VehicalNo, Model, ... ) and put in one main List keeping eye using ObservableObject?
I don't know eventhough it is being added to list as well as I have implemented INotifyPropert using ObservableObject, why its not reflecting in front-end.
Please help.
Add
public ObservableCollection ListItems {get{return listItems;}}
to you VehicalMainViewModel and change binding to
ItemsSource="{Binding ListItems}"
P.S. your listItems is private field.
ok so heres an example of the first Text box and you can follow suit on the rest:
<Window x:Class="Seris.VehicalForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Seris.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:VehicalMainViewModel/>
</Window.DataContext>
<Label Content="Vehical No" HorizontalAlignment="Left"/>
<TextBox Name="VehicalNo_Text" Height="23" TextWrapping="Wrap" Text="{Binding VehicalNo}" HorizontalAlignment="Left"/>
I cannot find any references at the moment but I would suggest looking at DataContext and Data Binding in WPF
EDIT
<Application x:Class="Seris.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="VehicalForm.xaml">
<Application.Resources>
</Application.Resources>
</Application>

Categories

Resources