We are a newbie for Xamarin. We are having an issue in binding the response data from a web service to a ListView.
We debugged and we can see the the web service is successfully responding with the data but it never gets populated.
Any ideas?
It's gotta be a small thing that we are missing. We have managed to display a single entry from the data with other views (in other parts of the project) BUT not in IEnumerable<> or List<>
Here's the code:
View - RoundsPage.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:AthlosifyMobile.ViewModels"
x:Class="AthlosifyMobile.Views.RoundsPage">
<ContentPage.BindingContext>
<viewModels:RoundsViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Entry Text="{Binding AccessToken}" />
<Button Command="{Binding GetRoundsCommand}" Text="Get all rounds" />
<Label Text="Rounds: "></Label>
<ListView ItemsSource="{Binding Rounds}" HasUnevenRows="true" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="Round 1:"></Label>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding DailyHandicap}"></Label>
<Label Text="{Binding PlayedUTC}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>`
ViewModel - RoundsViewModel.cs :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using AthlosifyMobile.Annotations;
using Xamarin.Forms;
using AthlosifyMobile.Services;
using AthlosifyMobile.Models;
namespace AthlosifyMobile.ViewModels
{
public class RoundsViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<Round> _rounds;
public string AccessToken { get; set; }
public IEnumerable<Round> Rounds
{
get
{
return _rounds;
}
set
{
_rounds = value;
OnPropertyChanged();
}
}
public ICommand GetRoundsCommand
{
get
{
return new Command(async() =>
{
Rounds = await _apiServices.GetRoundsAsync(AccessToken);
});
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model - Course.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AthlosifyMobile.Models
{
public class Round : EntityBase
{
public Guid RoundID { get; set; }
public Guid UserID { get; set; }
public Guid RoundCategoryID { get; set; }
public Guid CourseID { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public int DailyHandicap { get; set; }
public DateTime PlayedUTC { get; set; }
public RoundCategory RoundCategory { get; set; }
public Course Course { get; set; }
public ICollection<RoundHole> RoundHoles { get; set; }
}
public abstract class EntityBase
{
public DateTime CreatedUTC { get; set; }
public DateTime LastModifiedUTC { get; set; }
}
}
Services - apiservices.cs:
using AthlosifyMobile.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace AthlosifyMobile.Services
{
public async Task<IEnumerable<Round>> GetRoundsAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync("http://localhost:5609/api/Rounds");
var list = JsonConvert.DeserializeObject<IEnumerable<Round>>(json);
return list;
}
}
}
You will need to diagnose whether this is an issue with connecting the View to the ViewModel or whether your Data Service isn't working correctly. Either way, there are a few things you should do to fix this!
Firstly you are using IEnumerable, instead you should be using ObservableCollection<T>. You should always be using ObservableCollection<T> for Binded list views. This is explained in the xamarin docs here (they automatically notify the view when their contents changed & update).
So you should make this change:
public ObservableCollection<Round> Rounds { get; }
Next you should verify that the bindings are correct. I would not recommend your approach of going straight to live data if you aren't familiar with xamarin. Firstly you should try adding some static objects to the view model and trying to bind them!
Disconnect your API code and call a method that creates some of your Round objects. Here is an example method (i use methods like these all the time when designing my ListViews UI).
public RoundsViewModel()
{
Rounds = CreateSampleData();
}
private ObservableCollection<Round> CreateSampleData()
{
ObservableCollection<Round> dummyData = new ObservableCollection<Round>();
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
dummyData.Add(new Round() { Name="User", handicap=1, PlayedUTC=DateTime.Now });
return dummyData;
}
At this point you will either see items in your ListView, meaning you have an issue with your API code / Implementation of INotifyPropertyChanged. If you don't see anything then you likely have an issue with binding and will need to verify that your view is actually connected to the View Model.
Mvvm Helpers
Seeing some of this code makes me feel very sorry for you, you definitely should looking into using an MVVM helper such as Prism or MVVMCross. I personally use Prism which provides a ViewModelBase which all ViewModels inherit from. This means all of the INotifyPropertyChanged code is hidden away from you (less boilerplate). It also has a dependancy service which means hooking views up to view models is as simple as registering it in the app.cs.
If you are interested in prism, watch this video with Brian Lagunas to see what Prism can do for you!
Update: There are now a few helpful libraries aside from Prism that will help with the MVVM stuff. Refractored.MVVMHelpers and Xamarin.CommunityToolkit both contain an essential object: ObservableRangeCollection.
ALL code using an ObservableCollection should be replaced with ObservableRangeCollection, it is an essential object and really belongs in a microsoft maintained namespace at this point. It creates a performance benefit for updating larger collections & reduces the need for alot of boilerplate when updating the ObservableCollection
Related
I just encountered this issue on a project i am working on, and am stumped. As you can see in the gif below, when selecting an item with the Picker dialog on iOS (Emulator), the selected value jumps to the last item in the list after confirming (no matter if I tap out of the dialog or use the Done button). On Android the corresponding dialog behaves properly. I am using Xamarin.Forms 4.5.0.356 and Xamarin.Essentials 1.5.1 for this.
Minimum Bug replica
public class PickerItemModel
{
public string TestProp { get; set; }
public PickerItemModel(string t) => TestProp = t;
public override string ToString()
{
return TestProp;
}
}
public class MainViewModel : ComputedBindableBase
{
public List<PickerItemModel> PickerItemModels { get; set; } = new List<PickerItemModel> {
new PickerItemModel("Hello"),
new PickerItemModel("Stackoverflow")
};
public PickerItemModel SelectedModel { get; set; }
The ComputedBindableBase does implemented the INotifyPropertyChanged Event and automatically raise it when a property is changed.
<Picker Title="Test"
ItemsSource="{Binding PickerItemModels}"
SelectedItem="{Binding SelectedModel}" />
The question is now, either how do i fix this behavior, or what is valid workaround for it. I am willing to use some custom packages for this, but I cannot implement the complete dialog on my own as I have some restrictions on the project in terms of time spent on it.
Thank you in advance for your time. :-)
EDIT:
I recompiled the code now after a few hours and it works now. I assume it was some bug with old code not being properly redeployed or something... I am also using VS on Mac so that may also just be the cause since it has behaved buggy since day one...
Also, here is the BindableBase class (ComputedBindableBase does nothing but add an utility function):
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T target, T value, [CallerMemberName] string propertyName = "")
{
target = value;
RaisePropertyChanged(propertyName);
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I can not repro this issue using Xamarin.Forms 4.5.0.356 and Xamarin.Essentials 1.5.1. My code is functionally the same as yours with the one exception that I did not derive my viewmodel from ComputedBindableBase. The following code works as expected
View
<?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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:app2="clr-namespace:App3"
mc:Ignorable="d"
x:Class="App3.MainPage">
<ContentPage.BindingContext>
<app2:MainPageViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<Picker Title="Please select..."
ItemsSource="{Binding PickerItemModels}"
SelectedItem="{Binding SelectedModel}" />
</ContentPage.Content>
</ContentPage>
ViewModel
public class PickerItemModel
{
public string TestProp { get; set; }
public PickerItemModel(string t) => TestProp = t;
public override string ToString() => TestProp;
}
public class MainPageViewModel
{
public List<PickerItemModel> PickerItemModels { get; set; } = new List<PickerItemModel> {
new PickerItemModel("Hello"),
new PickerItemModel("Stackoverflow")
};
public PickerItemModel SelectedModel { get; set; }
}
This app works just fine in UWP. I have ripped out everything except one of the more basic properties that is failing on Android. It looks like this:
MyPage.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:MyApp.ViewModels"
x:Class="MyApp.Views.MyApp">
<ContentPage.BindingContext>
<ViewModels:MyViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView>
<StackLayout Style="{StaticResource PageForm}">
<Picker ItemsSource="{Binding Modes}"
ItemDisplayBinding="{Binding Value}"
SelectedItem="{Binding SelectedMode}" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
MyPage.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyApp : ContentPage
{
public MyApp ()
{
InitializeComponent ();
}
}
}
MyViewModel.cs
using MyApp.Models;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace MyApp.ViewModels
{
public class MyViewModel: INotifyPropertyChanged
{
List<Mode> _modes;
Mode _selectedMode;
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
Modes = new List<Mode>()
{
new Mode() { Key=ModeType.Mode1, Value="Mode1" },
new Mode() { Key=ModeType.Mode2, Value="Mode2" }
};
SelectedMode = Modes.Single(m => m.Key == ModeType.Mode1);
}
public List<Mode> Modes {
get { return _modes; }
set {
_modes = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Modes"));
}
}
public Mode SelectedMode {
get {
return _selectedMode;
}
set {
_selectedMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedMode"));
}
}
}
}
Mode.cs
namespace MyApp.Models
{
public enum ModeType { Mode1, Mode2 };
public class Mode
{
public ModeType _key;
public string _value;
public ModeType Key {
get
{
return _key;
}
set
{
_key = value;
}
}
public string Value {
get
{
return _value;
}
set
{
_value = value;
}
}
}
}
and what I see in the Debug console is
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'Value' property not found on 'MyApp.Models.Mode', target property: 'Xamarin.Forms.Picker.Display'
[0:] Binding: 'SelectedMode' property not found on 'MyApp.ViewModels.'MyApp', target property: 'Xamarin.Forms.Picker.SelectedItem'
Like I said this works if I run it as a UWP app but when I try it on Android it just doesn't work. That's about all I can say since it doesn't really say what the problem is other than the errors above which don't make sense.
The rest of the view model actually works. The main part of the app works, I can even run the code on this view model. If I create a simple string binding that will work, even on Android.
Any help is appreciated.
The answer is total magic to me. If someone can please explain this I will mark your answer as the accepted one.
Anroid Project File > Properties > Linking > Set to None.
It still didn't work so I closed Visual Studio and deleted the bin and obj directories in the PCL and Android projects. Finally it worked.
One other thing is this seems like I've now lost the ability to have linking be set to sdk and user assemblies. What if I need that at some point?
Use a one way binding to avoid having these binding errors in the debug console.
Text="{Binding [Name], Source={x:Static i18n:Translator.Instance}, Mode=OneWay}"
If you need TwoWay binding, make sure the bound model objects implement INotifyPropertyChanged as Markus Michel indicated.
Your mode model class also needs to implement INotifyPropertyChanged
I'm trying to Graph Excel data using ChartJS.
Visual Studio is saying that List<Graphs> does not contain a definition for Answers.
I can't find anything wrong with my code, though. Though, I've only been using VS for the past two days.
Can someone look at my code and maybe find a mistake, or two? Thanks!
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using ReinovaGrafieken.Models;
namespace ReinovaGrafieken.Models
{
public class GraphDataViewModel
{
public List<Graphs> GraphData { get; set; }
}
}
Graphs Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using ReinovaGrafieken.Models;
namespace ReinovaGrafieken.Models
{
public class Graphs
{
public string Names { get; set; }
public string AnswerHeaders { get; set; }
public int Answers { get; set; }
public string Questions { get; set; }
public string AnteOrPost { get; set; }
}
}
And a piece of the code from the View:
#model ReinovaGrafieken.Models.GraphDataViewModel
#{
ViewBag.Title = "Dashboard";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/bootstrap")
<h2>Dashboard</h2>
<div id="chart_container">
<canvas id="bar_chart"></canvas>
<script>
var answers = #Html.Raw(Json.Encode(#Model.GraphData.Answers));
var labels = #Html.Raw(Json.Encode(#Model.GraphData.Names));
This is where I got the ideas from, where it does work for that person:
https://www.youtube.com/watch?v=E7Voso411Vs&t=2787s
GraphData is collection of Graph. So Answers is accessible property on Graph and not GraphData.
Like #Stephen Muecke wrote, List<T> does not contain a property named Answers.
You could take element, which you need like this:
#Model.GraphData.First().Answers
(get first element from the list).
Or you can use .Foreach() method
Want to see if someone could help clear this up for me? Is there an advantage to DataBinding from the XAML Element to a value within the ViewModel(ex:1), or from the CodeBehind(ex:2) back to the Element like... HostName.Text?
<TextBlock Text="{Binding HostName}" /> --- (ex:1)
<TextBlock Name="HostName" /> --- (ex:2)
POGO
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Task;
namespace AppName.Models
{
public class Contact
{
[Key]
public int Id {get; set;}
public string Team { get; set;}
public string FirstName { get; set;}
public string LastName { get; set;}
public string Phone { get; set;}
public string Email { get; set;}
public string Role { get; set;}
public string DisplayName => $"[LastName}, {FirstName}";
}
}
The simple answer is that it depends on your application and needs. When you're building a small application, using code behind or data binding to view models doesn't make much difference. It's easy to understand the flow and when to make updates. But as your app complexity goes up and your need to test your code, then you start to use patterns that makes your code more maintainable and testable. That's where the MVVM pattern came from.
Testing code in your code behind file is harder than just testing your business logic in your ViewModel class, and ensuring it works as expected.
Your example above is kind of simplistic because it's a TextBlock that only displays text and doesn't take input. A TextBox is a better example for binding since data can change in the view model or in the UI. Binding lets you back the displayed text with a property, so changes from any direction update the model property and UI automatically.
<TextBox x:Name="Entry" Text="{Binding SelectedValue , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
ViewModel:
public class CatalogViewModel : BindableBase
{
private string selectedValue;
public string SelectedValue
{
get { return selectedValue; }
set { SetProperty<string>(ref selectedValue, value); }
}
...
}
The alternative to that is a lot of code in the code-behind file to keep things in sync between the TextBox and data elements.
<TextBox x:Name="Entry2" TextChanged="Entry2_TextChanged" />
Code behind:
private string entryText;
public string EntryText
{
get { return entryText; }
set
{
if (value != entryText)
{
entryText = value;
Entry2.Text = entryText;
}
}
}
private void Entry2_TextChanged(object sender, TextChangedEventArgs e)
{
entryText = Entry2.Text;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// initialize controls
EntryText = "Default";
}
You've now coupled your business logic with the page layout and changes to either will cause a lot of changes. And testing the behavior of your code is harder to write and harder to mock.
And it just gets more complicated with multiple input controls and more complex controls, like ListViews and GridViews.
You should read up on MVVM if you're interested in the benefits of using view models and databinding to them: https://msdn.microsoft.com/en-us/magazine/dd419663.aspx.
I'm going to do a wpf application using MVVM(It based on http://www.codeproject.com/KB/WPF/MVVMQuickTutorial.aspx
).
This application will be connecting with webservice one per month.
On webservice I have contract
public class Student
{
public string Name {get; set;}
public int Score {get; set;}
public DateTime TimeAdded {get; set;}
public string Comment {get; set;}
}
In WPF application Adding, and removing students will be saving to xml file.
So at wpf application Student would be something like :
public class Student
{
public string Name {get; set;}
public int Score {get; set;}
public DateTime TimeAdded {get; set;}
public string Comment {get; set;}
public Student(string Name, int Score,
DateTime TimeAdded, string Comment) {
this.Name = Name;
this.Score = Score;
this.TimeAdded = TimeAdded;
this.Comment = Comment;
}
}
public class StudentsModel: ObservableCollection<Student>
{
private static object _threadLock = new Object();
private static StudentsModel current = null;
public static StudentsModel Current {
get {
lock (_threadLock)
if (current == null)
current = new StudentsModel();
return current;
}
}
private StudentsModel()
{
// Getting student s from xml
}
}
public void AddAStudent(String Name,
int Score, DateTime TimeAdded, string Comment) {
Student aNewStudent = new Student(Name, Score,
TimeAdded, Comment);
Add(aNewStudent);
}
}
How connect this two classes ?
The worst think I guess is that contract Student from webservice would be use in this wpf application to get students from xml, in other application collection of studetns would be getting from database.
I'm newbie in design patterns so it is very hard for me :/
Example: I click AddUser, and in application A it calls webservice method which adding user to database, in application B it adds user to XML file, and In application.
Base class are contracts at webservice.
Next explanation:
First application uses webservice to save data at database. Second application never save data in xmls and one perm month send this xmls to webservice and convert their to intances of students and save it at database
It is very unclear from your question what the actual problem is. But, I guess I could address a few and show you a way to solve them.
1) The main problem I see in your project is you have two definitions of Student class. You can easily merge them into a single definition. (I will just show you how...)
2) It is very unclear whether you want your WPF client to save data to a Data Source (XML?) or your Web Service should do it. And if the WPF client is supposed to save the Students then what is the Web Service for?
3) You don't have a ViewModel defined anywhere for the Student class which in this case is Model.
I have created an example with 3 projects.
1) WebService - A WCF Service Project
2) StudentLib - A Class Library Project (where Student class is defined)
3) DesktopClient - A WPF Application Project
Here is the source code :
WebService.IStudentService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using StudentLib;
namespace WebService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IStudentService" in both code and config file together.
[ServiceContract]
public interface IStudentService
{
[OperationContract]
StudentLib.Student GetStudentById(Int32 id);
[OperationContract]
void AddStudent(StudentLib.Student student);
}
}
WebService.StudentService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using StudentLib;
namespace WebService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "StudentService" in code, svc and config file together.
public class StudentService : IStudentService
{
public StudentLib.Student GetStudentById(int id)
{
return new StudentLib.Student() { Name = "John Doe", Score = 80, TimeAdded = DateTime.Now, Comment = "Average" };
}
public void AddStudent(StudentLib.Student student)
{
// Code to add student
}
}
}
WebService's Web.Config
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings />
<client />
<services>
<service name="WebService.StudentService" behaviorConfiguration="metaDataBehavior">
<endpoint address="basic" binding="basicHttpBinding" contract="WebService.IStudentService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metaDataBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
StudentLib.Student.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace StudentLib
{
[DataContract]
public class Student
{
[DataMember]
public String Name { get; set; }
[DataMember]
public Int32 Score { get; set; }
[DataMember]
public DateTime TimeAdded { get; set; }
[DataMember]
public String Comment { get; set; }
}
}
DesktopClient.StudentViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DesktopClient
{
class StudentViewModel
{
protected StudentLib.Student Student { get; set; }
public StudentViewModel(StudentLib.Student student)
{
this.Student = student;
}
public String Name { get { return Student.Name; } }
public Int32 Score { get { return Student.Score; } }
public DateTime TimeAdded { get { return Student.TimeAdded; } }
public String Comment { get { return Student.Comment; } }
}
}
DesktopClient.MainWindow.xaml
<Window x:Class="DesktopClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="400"
Height="300"
Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
Grid.Row="0">Name :</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="0"
Text="{Binding Name}"></TextBlock>
<TextBlock Grid.Column="0"
Grid.Row="1">Score :</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{Binding Score}"></TextBlock>
<TextBlock Grid.Column="0"
Grid.Row="2">Time Added :</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="2"
Text="{Binding TimeAdded}"></TextBlock>
<TextBlock Grid.Column="0"
Grid.Row="3">Comment :</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="3"
Text="{Binding Comment}"></TextBlock>
</Grid>
</Window>
DesktopClient.MainWindow.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;
using DesktopClient.StudentService;
using StudentLib;
namespace DesktopClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
IStudentService client = new StudentServiceClient();
Student student = client.GetStudentById(1);
DataContext = new StudentViewModel(student);
client.AddStudent(new StudentLib.Student() { Name = "Jane Doe", Score = 70, TimeAdded = DateTime.Now, Comment = "Average" });
}
}
}
Here all the above mentioned problems are resolved :
1) The Student class is defined in a common assembly (StudentLib) referenced by both WebService project and DesktopClient project. So, while adding a Service Reference, that class is reused by the code generator.
2) I recommend all the storage related operations be in the Web Service and the Client app should just use Web Service to store data.
3) StudentViewModel class is used instead of Student class to display data in MainWindow.
You can use the following architecture.
1) WPF Application (Application A & B) with a app.config which contains either Web Service URL or XML file path
2) Business/Data Layer
Create a proxy class for the Web service URL and create the Student Model class which you mentioned in your post.
Modify your Add method to support both functionalities. Call Add method in Webservice if app.config has webservice url (App A) or call add to XML method if appsetting has XML filepath(App B)
Fortunately, the classes are already "linked", at least in form: because the ViewModel is just a bunch of Models.
In the MVVM pattern, you'll want to handle your data-binding functions in the ViewModel. This includes the following:
The StudentModel constructor (private StudentsModel() { ...) should load itself with all the Student instances. (Alternately, you could use a separate Load() function--which seems most logical if you have a Save() method as well.) Here, you would presumably read the XML file, use an XmlSerializer to deserialize the XML data into a collection of students (probably List), then add them to itself using either the base constructor (for the whole list) or the Add() method (one at a time, e.g. in a loop).
You would need functions to add Students to the collection, as in the tutorial example. On the desktop app, here is where you want to call the Add function on the web service, and if it's successful you add it to your own collection in memory. You then have to decide if you want to immediately save the (serialized) data into the XML file, or do it all together later (for example, when unloading the object or calling a Save() method).
You probably want to include a method to remove Student objects from the collection, too. The public methods of the ObservableCollection will be some help here, but you may want/need more logic than that in choosing which object(s) to remove. And again, here you must notify the web service about the deleted items, and know whether you're going to save changes right away or wait for a separate event.
Inheriting from the ObservableCollection is wise, as that gets you a lot of the magic that makes the binding dynamic--I mean, where the UI is updated as the data changes. As long as your using the base methods of Add() and Remove() etc. you are getting the benefits of IEnumerable, INotifyPropertyChanged, and INotifyCollectionChanged.
You may notice I mostly mentioned XML serialization in the explanation above, but for making a web service with similar functionality, you'll swap out ideas like "serialize the XML and save into a file" for "save changes into your database". The pattern is the same, it's just the actual actions (implementation) is different.
In short, the ViewModel here is where you want to implement all the stuff that loads & handles the data in memory and saves it out to the file, database, or web service.