My ViewModel does not provide current variable value - c#

[EDIT] Example reprodoucing this issue Host to AddIn issue
I'm trying to write a host application which is extendable and uses few interfaces. One of them is
public MessageCreator GetMessageCreator()
{
var creator = new AVL2MessageCratorFactory();
return creator.Create(new AVL2DataForMessageCreatingImpl { Imei = VM.Imei });
}
from AddIn and below you can find XAML of AddIn guest application:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AVL2SimulatorAddView="clr-namespace:AVL2SimulatorAddView" x:Class="AVL2SimulatorAddView.AVL2AddInUI">
<Grid Height="46" Width="344">
<Grid.DataContext>
<AVL2SimulatorAddView:AVL2ViewModel x:Name="VM" />
</Grid.DataContext>
<Label Content="Starting IMEI" Margin="0,0,255,0" Height="29" VerticalAlignment="Top" />
<TextBox Text="{Binding Path=Imei, UpdateSourceTrigger=PropertyChanged }" Margin="120,2,48,0" Width="176" Height="27" VerticalAlignment="Top" />
</Grid>
Imei property from ViewModel
using System.ComponentModel;
namespace AVL2SimulatorAddView
{
public class AVL2ViewModel : INotifyPropertyChanged
{
readonly AVL2Model _avl2Model = new AVL2Model();
public string Imei
{
get { return _avl2Model.Imei; }
set
{
if (value == _avl2Model.Imei) return;
_avl2Model.Imei = value;
OnPropertyChanged("Imei");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
in debug mode I see that property in ViewModel is updated every character is written inside TextBox, but when GetMessageCreator() from Host application is called it seems like new, empty VM and its Imei is returned (I do not see Imei on Host aplication side)
Am I missing some protection in .Net against bypassing bound data? Other "hardcoded" strings are passing well. MVVM and DataContext on Host Application side works well also. I tried different types of UpdateSourceTrigger, but it always seems to work on AddIn side, and it does not provide current date to shared interface.
[EDIT]
Other control bound to Imei, display the changes on-line, when data is entered in textbox
Calling local button set properly the label content
private void button1_Click(object sender, RoutedEventArgs e)
{
label1.Content = VM.Imei;
}
Temporary calling method from Host application
var msgCreator = _tabsMap[(TabItem) tabControl1.SelectedItem].GetMessageCreator();

I'm referring to your demo project. Consider this code in the constructor of LoadGeneratorWPFHostApplication.MainWindow:
// Activate creates instance AAddIn#1 (wpfAddInHostView.wpfAddInContract.wpfAddInView)
foreach (var wpfAddInHostView in addInTokens.Select(addInToken => addInToken.Activate<IWpfAddInHostView>(AddInSecurityLevel.Host)))
{
// AAddin#1.GetAddinUI creates instance AAddIn#2 (tabItem.Content)
var tabItem = new TabItem {Content = wpfAddInHostView.GetAddInUI(), Header = wpfAddInHostView.GetName()};
tabControl1.Items.Add(tabItem);
// adding AAddIn#1 to _tabsMap (but using AAddIn#2 on the UI).
_tabsMap.Add(tabItem, wpfAddInHostView);
}
So what happened here is you use System.AddIn.Hosting.AddInToken.Activate to create an instance of AAddIn (#1). But right after that, you use this AAddIn#1 to create another instance with its method GetAddInUI.
public partial class AAddIn : WPFAddInView
{
public FrameworkElement GetAddInUI()
{
return new AAddIn(); // creates a new instance of itself
}
}
AAddIn#1 is persisted on the contract (WPFAddIn_ContractToViewHostSideAdapter.wpfAddInContract.wpfAddInView) and in _tabsMap, AAddIn#2 is used in the UI (tabItem.Content).
There are different ways to ensure you keep the same instance, I would probably just remove the GetAddInUI method from the WPFAddInView interface (note: always start interface names with an I) as well as the AAddIn class, then return the wpfAddInView directly on the contract:
public class WPFAddIn_ViewToContractAddInSideAdapter : ContractBase, IWPFAddInContract
{
public INativeHandleContract GetAddInUI()
{
FrameworkElement fe = this.wpfAddInView as FrameworkElement; // return instance directly instead of creating new one by wpfAddInView.GetAddInUI();
INativeHandleContract inhc = FrameworkElementAdapters.ViewToContractAdapter(fe);
return inhc;
}
}
Side note: Use the method .GetHashCode() in debugger to differentiate between instances of the same class.

Related

WPF Data Binding To Self Updating Model

Please can you advise if this is possible or what approach is best?
I will add my code afterwards but I am worried that it will add limited value to what I am trying to ask/explain.
The WPF examples that I have seen implement
A (poco) model e.g Customer.
Then they implement a view model (MVVM pattern). (The View Model needs to implement ObservableCollection and or implement INotifyPropertyChanged interface so that any changes in the model are reflected in the UI once the model changes.)
In the xaml code behind, in the page constructor, the ViewModel is passed to the DataContext.
The xaml can then bind to this View Model Data with a Mode of update e.g. TwoWay.
Here is what I need to understand.
I have implemented my own data model / class, which has async tasks constantly updating the status of different fields in this class.
My model resides in a separate class library that I would like to inject/supply it to a view model. However, since my object/class is 'self-updating', I can't simply copy across values into my view model - since they will change over time. I want my view model to be aware of changes that underlying values and show these changes in the UI as the async tasks update the model.
How do I go about this? So in my example, my Customer object will be self-updating, some background task would add/remove customers in a class library outside of my ViewModel.
I hope that I have managed to ask my question in a way that is clear to understand.
The XAML binding to the Customer View Model
<ListView Grid.Row="1" ItemsSource="{x:Bind ViewModel.Customers,Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedCustomer,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Customer">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Bind FirstName,Mode=OneWay}" FontWeight="Bold"/>
<TextBlock Text="{x:Bind LastName,Mode=OneWay}"
Margin="5 0 0 0"/>
<TextBlock Text="(Developer)" Margin="5 0 0 0" Opacity="0.5"
Visibility="{x:Bind IsDeveloper,Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Reference for this sample code
public class MainViewModel : Observable
{
private ICustomerDataProvider _customerDataProvider;
private Customer _selectedCustomer;
public MainViewModel(ICustomerDataProvider customerDataProvider)
{
_customerDataProvider = customerDataProvider;
Customers = new ObservableCollection<Customer>();
}
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
if (_selectedCustomer != value)
{
_selectedCustomer = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsCustomerSelected));
}
}
}
public bool IsCustomerSelected => SelectedCustomer != null;
public ObservableCollection<Customer> Customers { get; }
public async Task LoadAsync()
{
Customers.Clear();
var customers = await _customerDataProvider.LoadCustomersAsync();
foreach (var customer in customers)
{
Customers.Add(customer);
}
}
public async Task SaveAsync()
{
await _customerDataProvider.SaveCustomersAsync(Customers);
}
public void AddCustomer()
{
var customer = new Customer { FirstName = "New" };
Customers.Add(customer);
SelectedCustomer = customer;
}
public void DeleteCustomer()
{
var customer = SelectedCustomer;
if (customer != null)
{
Customers.Remove(customer);
SelectedCustomer = null;
}
}
}
The INotifyPropertyChanged is implemented here:
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Thanks #Bandook for your comments. Sometimes the coding is the easy part, but the thought behind and the process of fitting the parts together in a simple and coherent way can be more difficult.
The solution I implemented was as follows:
As per the customer example, I had had to implement methods that updated my class properties that implemented the INotifyPropertyChanged interface. (This was almost identical to the Customer code.) The result of this is that any changes to the underlying model are reflected in the UI.
The kicker was this. I implemented a Publish Subscribe Design Pattern in my class library - that continually refreshed the data. My 'Customer' class had to then had to subscribe to change events published by the class library.
There was one problem, however, the thread for the Class Library code and that of the application thread was not the same, resulting in a clash.
The article here allowed me to solve this issue.
In summary, the solution was to implement a Publish Subscribe Design Pattern. My class library published updates and my View Model class (similar to the customer class) subscribed to the events it published.

Pass file between pages - UWP C#

I'm wondering what is the best way to pass a file between pages in a UWP app?
I have a UWP app with two pages. In the first page, I have the user open a file with filepicker and load that file into a media player.
I want to pass that same file onto the second page when the user navigates there. I am passing the file over currently as a string which I then am attempting load as a storagefile using GetFileFromPathAsync.
This currently works as I'm able to load the file on the second page but it requires that the user provide broad file system access.
Code on Page 1 (FileLoaded is file path string):
private async void TranscodeMedia_Click(object sender, RoutedEventArgs e)
{
AppWindow appWindow = await AppWindow.TryCreateAsync();
Frame appWindowContentFrame = new Frame();
appWindowContentFrame.Navigate(typeof(TranscodeMedia), FileLoaded);
Code on Page 2:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var fileTransfer = e.Parameter.ToString();
FileName.Text = fileTransfer;
StorageFile PassedFile = await StorageFile.GetFileFromPathAsync(fileTransfer);
I'm wondering if this is the best way to pass the file between pages? I'd rather not require the user to provide broad system access to the app if possible. Any help you can provide is most appreciated!
The best and most standard way in C#/WPF/UWP way is to use a standard pattern that consist of a general ViewModel class (which contains all the common app data that you want to use in the logic layer), put as a field in the static MainPage (or even in the App.xaml.cs class).
I always do it like this:
1) I use the MainPage automatically created as the "shell" of the app, with a property that is the AppViewModel.
The MainPage (and thus the AppViewModel) can be accessed from everywhere in the app, by setting itself as a static field in its own class (the "Current" static field can be called from everywhere in the app... even in a MessageDialog class!).
This is the code for the MainPage (or a shell Page that you wish, but I suggest doing like this, it is a pretty standard way used even by Microsoft), simpler than you think:
public sealed partial class MainPage : Page
{
public AppViewModel ViewModel { get; set; } = new AppViewModel();
public static MainPage Current { get; set; }
public MainPage()
{
this.InitializeComponent();
Current = this;
}
}
THIS is the trick: to make the page static in one field in its
own class, so that that static field will be UNIQUE in the entire app
(this is one of the main features of the "static" word) and, thus, by calling
MainPage.Current.ViewModel you can immediately get any data (in your
specific case, a StorageFile) stored there.
2) The AppViewModel itself is a class that must implement the INotifyPropertyChanged interface, in order to enable bindable properties and functions.
It is common, among Windows developers, to create a base class that implements it and then derive all the classes that needs bindable (i.e. observable) properties from it.
Here it is, exactly how Microsoft itself creates it:
public class BaseBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then you derive AppViewModel class (and all the other model and viewmodel classes) from it… populating it with all the common properties that you need to share across pages.
I have even added a derived property, in order to show how you can share even multiple data types at once, and a function:
public class AppViewModel : BaseBind
{
public AppViewModel()
{
// Usually we initialize all the starting data here, in the viewmodel constructor...
}
// All common app data
private string sampleCommonString;
public String SampleCommonString
{
get { return sampleCommonString; }
set { SetProperty(ref sampleCommonString, value); OnPropertyChanged(nameof(SampleDerivedProperty1)); OnPropertyChanged(nameof(SampleDerivedProperty2)); }
}
public String SampleDerivedProperty1 => "return something based on SampleCommonString";
public String SampleDerivedProperty2
{
get
{
// evaluate in some way SampleCommonString...
return "Same thing as SampleDerivedProperty1, but it allows to add more than just one istruction";
}
}
// This is a property that you can use for functions and internal logic… but it CAN'T be binded to the UI directly
public String SampleNOTBindableProperty { get; set; }
public void SampleFunction()
{
// Insert code, that needs to interact with all the data contained in the viewmodel itself, here...
// The function has to be with NO parameters, in order to work with simple {x:Bind} markup.
// If your function has to access some specific data, you can create a new bindable (or non) property, just as the ones above, and memorize the data there.
}
}
3) Then, in order to access all this from another Page, just create an AppViewModel field in that page, referencing the viewmodel contained in the static mainpage:
public sealed partial class SecondPage : Page
{
public AppViewModel ViewModel => MainPage.Current.ViewModel;
public SecondPage()
{
this.InitializeComponent();
}
}
...and you can easily bind XAML controls properties to the AppViewModel itself:
<TextBlock Text="{x:Bind ViewModel.SampleCommonString, Mode=OneWay}"/>
<TextBox Text="{x:Bind ViewModel.SampleCommonString, Mode=TwoWay}"/>
<Button Content="Sample content" Click="{x:Bind ViewModel.SampleFunction}"/>
(Mode=OneWay is for real-time binding, in order that the property is immediately updated even in the UI, while Mode=TwoWay is used for those properties that can be edited from the control itself, by the user, in order to interact with app logic).
In this mode you will be able to display data and all its changes in real-time!
So... this is the way to keep all the app data at run-time in a
correct and flexible way... by learning it and practicing, in the
future you will use this pattern even in a smarter way, by creating
viewmodels for every object of your application (for example: if
your app need to store your company's customers data, you will have a
"CustomerViewModel" class derived from the BaseBind class, with all
the data of a customer in it) and creating lists like
ObservableCollection<SampleViewModel> to store all of them (ObservableCollection<t> is a collection type that has built-in mechanism to handle list changes, like adding, removing and reordering list items).
Then you will link every observable collection to the ItemsSource property of a control that inherits from ListBase class (tipically: ListView or GridView), creating a DataTemplate to display each list item, like in this example:
<Page
xmlns:vm="using:SampleApp.ViewModelsPath"
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.SampleListOfObjectViewModel, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:SampleObjectViewModel">
<StackPanel>
<TextBlock Text="{x:Bind SampleObjectProperty1, Mode=OneWay}"/>
<TextBlock Text="{x:Bind SampleObjectProperty2, Mode=OneWay}"/>
<Button Click="{x:Bind SampleObjectFunction}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
...and all the data displayed will be updated in real-time whenever you change it!
Hope this all will help you boost your knowledge about how preparing a WPF/UWP logic layer, because all of this works pretty in the same way even for the WPF apps (i.e. the old desktop programs).
Best regards
There are some other ways to implement your requirement about accessing the same file on different pages. But for your scenario, you could use Future-access list in your UWP app.
By picking files and folders, your user grants your app permission to access items that might not be accessible otherwise. If you add these items to your future-access list then you'll retain that permission when your app wants to access those items again later.
Here is the sample code I made
In the first page:
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
// add file to the Future Access list
var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
// this token is the key to get the file.
string FALToken = storageItemAccessList.Add(file, "mediaFile");
// in your real scenario, you need to save the token and pass it when you nee
this.Frame.Navigate(typeof(TestPage), FALToken);
}
In the second page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string token = (string)e.Parameter;
var storageItemAccessList = StorageApplicationPermissions.FutureAccessList;
StorageFile retrievedFile = await storageItemAccessList.GetFileAsync(token);
}
So you don't need the broad file system access if you use Future-access list to keep the permission of files.
For more detailed information, please refer to this document: Track recently used files and folders

Passing data between view models in MVVM C#

I'm currently trying to create a "log" text box that gets messages between multiple view models (tied to multiple views) that I have. I've tried the approach described by user Blachshma here (Multiple Data Contexts in View) but it does not seem to be working.
I have three classes. Class AViewModel, Class BViewModel and Class ABViewModel.
The view for A binds to AViewModel using the following code in its constructor:
this.InitializeComponent();
this.model = new AViewModel();
this.DataContext = this.model;
The view for B and AB follows the same pattern.
The class structures are as follows:
public class A : INotifyPropertyChanged
{
private string log = string.empty;
public class A()
{
}
public string ALog
{
get
{
return this.log;
}
set
{
this.log = value;
this.NotifyPropertyChanged("ALog");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
/* Function that executes when relay command is clicked */
private void ExecuteCommand()
{
this.ALog += "here";
}
}
Class B is defined the same way with property BLog
Class ABViewModel has properties for each other view model
public class ABViewModel
{
public AViewModel AVM
{
get;
set;
}
public BViewModel BVM
{
get;
set;
}
}
In the xaml I simply have
<TextBox Text="{Binding ABViewModel.AVM}" />
My plan is to eventually using Multibinding to concatenate both logs together, but at the moment I can't even get the one View Model to update my string. It looks like my container view model ABViewModel isn't getting updated, but I don't really understand why, but I'm not entirely sure how to fix this.
Any suggestions are extremely appreciated!
Thanks!
Edit:
I debug my code and see that my string ALog is getting updated, but I don't see the change on the UI. For more information, I click a button that's connected to a RelayCommand in class A. This button invokes a method to connect to a COM port. I'm able to use the COM port from other view models successfully after opening it. The log is supposed to update saying that the com port was opened but I never see any text added to the log in the GUI even though the instance of ALog that I can debug through has the added text.
I can't use Prism or MVVM-light for this particular project.

LINQ to Twitter setting users account image c#

I am attempting to fetch the users profileImageURl and bind it to an image. I have got the following C# code:
namespace IIVVYTwitter
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel(this);
}
}
class ViewModel : INotifyPropertyChanged
{
public User AccountImageUrl { get; set; }
readonly Page page;
public ViewModel(Page page)
{
this.page = page;
LoadAccount = new TwitterCommand<object>(LoadAccountHolder);
}
public TwitterCommand<object> LoadAccount { get; set; }
void LoadAccountHolder(object obj)
{
PinAuthorizer auth =
new PinAuthorizer
{
Credentials = new LocalDataCredentials()
};
if (auth == null || !auth.IsAuthorized)
{
page.Frame.Navigate(typeof(oAuth));
return;
}
var twitterCtx = new TwitterContext(auth);
var accounts =
from acct in twitterCtx.Account
where acct.Type == AccountType.VerifyCredentials
select acct;
Account account = accounts.SingleOrDefault();
LinqToTwitter.User user = account.User;
new User
{
AccountImageUrl = user.ProfileImageUrl
};
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AccountImageUrl"));
}
}
}
}
Linked with this XAML:
<Page
x:Class="IIVVYTwitter.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:IIVVYTwitter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Button Command="{Binding LoadAccount}">
<Border>
<Image x:Name="accountPicture" Source="{Binding AccountImageUrl}" />
</Border>
</Button>
</Grid>
</Page>
When I run the command through a button click, the code runs but the source of the Image is not updated.
I'm pretty sure the way I have bound the source data is flawed.
Thanks in advance.
I do not see you setting the DataContext for the page anywhere. Without a properly defined DataContext, the Bindings you are using cannot be resolved to any data object. A simple example would be in the OnNavigatedTo method or the page's constructor, you need to do something like:
this.DataContext = new ViewModel()
or something similar. There are different ways to wire up the data context depending on how you want handle viewmodel creation, lifetime, etc., but hopefully this gets you on the right path. In your code, you could also call page.DataContext = this; in your constructor.
Update: You also need to fix the code when you fire the PropertyChanged event. You have
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ImageUrl"));
}
should be
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AccountImageUrl"));
}
Notice the change to the string passed for the property name. You were firing an change event for a property that did not exist.
Update #2: You are returning a type of User from AccountImageUrl. You should be returning the actual http url string, not a custom type. If User has a property that hanging off of it that points to the http Url, you could update the Xaml Binding to AccountImageUrl.PropertyThatContainsRealUrl
Update #3:
In your code, you have
new User
{
AccountImageUrl = user.ProfileImageUrl
};
This is setting the AccountImageUrl on the new User object. However, you are binding to the AccountImageUrl of the ViewModel. At some point, you have to set AccountImageURl to the image url string and raise PropertyChanged on AccountImageURL. So, either before or after creating a new User, you should say this.AccountImageUrl = user.ProfileImageUrl. Hopefully I am still following your code and the changes you have made in the comments correctly.

MVVM Web Service Data in both Design Time & Run Time

I recently watched John Papa's Service Patterns with SL from SL Firestarter 2010, which described a service pattern in MVVM Light that I'm currently trying to implement. I'll describe the process below, but wanted to first state that I've been able to get my 'design time' data working no problem. What I can't figure out is my run-time data. Unfortunately my client is stuck using old-school .asmx web services, and my hands are tied on this one.
From my ViewModel, I call out IAccountService, an interface I've set up with my one method: GetAccounts. From here, I use a ServiceProviderBase class to determine if the call came from designtime or runtime. When I call this method from design time, I load a DesignAccountService which uses a DesignAccount Model to populate fake data to ultimately display in my Gridview. It works, I'm pretty stoked.
When I call the GetAccounts method from runtime, . The DB guy here has written and tested a web service that returns data into a data table, and is then converted into an ObservableCollection. This Web Service is running inside the web project of the solution. I'm attempting to call this web service from my SL project & grab the observable collection... Alright, so code:
In my ViewModel:
protected TSMVVM.Services.IAccountService AccountService { get; set; }
public AccountDefinitionViewModel(TSMVVM.Services.IAccountService accountService)
{
AccountService = accountService;
LoadData();
}
public void LoadData()
{
LoadAccounts();
}
public void LoadAccounts()
{
Accounts = null;
AccountService.GetAccounts(GetAccountsCallback);
}
private void GetAccountsCallback(ObservableCollection<TSMVVM.Model.P.P_ACCOUNTS> accounts)
{
if (accounts != null)
{
this._accounts = accounts;
if (_accounts.Count > 0)
{
SelectedAccount = Accounts[0];
}
}
}
private ObservableCollection<TSMVVM.Model.P.P_ACCOUNTS> _accounts;
public ObservableCollection<TSMVVM.Model.P.P_ACCOUNTS> Accounts
{
get { return _accounts; }
set
{
_accounts = value;
RaisePropertyChanged("Accounts");
}
}
Interface:
public interface IAccountService
{
void GetAccounts(Action<ObservableCollection<TSMVVM.Model.P.P_ACCOUNTS>> getAccountsCallback);
}
AccountService
private ObservableCollection<TSMVVMCommonSVC.TSAccount> _account = new ObservableCollection<TSMVVMCommonSVC.TSAccount>();
private TSMVVMCommonSVC.CommonSoapClient CommonService;
private Action<ObservableCollection<TSMVVMCommonSVC.TSAccount>> _getAccountsCallback;
public AccountService()
{
}
public void GetAccounts(Action<ObservableCollection<TSMVVM.Model.P.P_ACCOUNTS>> getAccountsCallback)
{
_getAccountsCallback = getAccountsCallback;
Uri iSilverlightServiceUriRelative = new Uri(App.Current.Host.Source, "../Services/Common.asmx");
EndpointAddress iSilverlightServiceEndpoint = new EndpointAddress(iSilverlightServiceUriRelative);
BasicHttpBinding iSilverlightServiceBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);// Transport if it's HTTPS://
CommonService = new TSMVVMCommonSVC.CommonSoapClient(iSilverlightServiceBinding, iSilverlightServiceEndpoint);
CommonService.GetAccountCollectionCompleted +=new EventHandler<TSMVVMCommonSVC.GetAccountCollectionCompletedEventArgs>(CommonService_GetAccountCollectionCompleted);
CommonService.GetAccountCollectionAsync();
}
private void CommonService_GetAccountCollectionCompleted(object sender,TSMVVMCommonSVC.GetAccountCollectionCompletedEventArgs e)
{
if (e.Result.Length > 0)
{
foreach (TSMVVMCommonSVC.TSAccount item in e.Result) {
var acct = new TSMVVM.Model.P.P_ACCOUNTS() {
ACCOUNT_NUMBER = item.AccountNumber,
DESCRIPTION = item.AccountDescription
};
_account.Add(acct);
}
}
_getAccountsCallback(_account);
}
Now, if I put a breakpoint in my ViewModel on the GET for Accounts, (which is set to return _accounts), Accounts get set to a collection of items with 315 items in it. If I drill down into that collection, I can see that the data is successfully returned from my web service. In fact, at this breakpoint, if I head into my xaml (code functions virtually identically in a DataGrid instead of a telerik control),
<telerik:RadGridView ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}"
AutoGenerateColumns="False">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Path=ACCOUNT_NUMBER}" Header="Account Number" />
<telerik:GridViewDataColumn DataMemberBinding="{Binding Path=DESCRIPTION}" Header="Description" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>
With the breakpoint set, I can see that the Accounts variable, in my ItemsSource binding, is set to that collection of 315 items. However, the grid is empty. I Know my column bindings are bound to the correct items, but I can't figure out where to go from here.
Change this code:
if (accounts != null)
{
this._accounts = accounts;
with
if (accounts != null)
{
this.Accounts = accounts;
Because the event PropertyChanged isn't fired in the first code, and UI doesn't know anything about changes.

Categories