MVVM Web Service Data in both Design Time & Run Time - c#

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.

Related

Xamarin.Forms and Prism - How to pass data and navigate to another view?

This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋

C# : public observablecollection<ViewModel> instance pass items to protected observablecollection<ViewModel>

I have the following class which work just fine
public class RemoteSource {
ObservableCollection<RemoteDataViewModel> remote;
string[] _servers = new string[] {
"server",
"server",
"server",
"server",
"server"
};
public RemoteSource() {
remote = CreateDataSource();
}
protected ObservableCollection<RemoteDataViewModel> CreateDataSource() {
ObservableCollection<RemoteDataViewModel> res = new ObservableCollection<RemoteDataViewModel>();
ITerminalServicesManager _manager = new TerminalServicesManager();
foreach (var host in _servers) {
using (ITerminalServer srv = _manager.GetRemoteServer(host)) {
try {
srv.Open();
foreach (ITerminalServicesSession session in srv.GetSessions()) {
res.Add(new RemoteDataViewModel() { Server = srv.ServerName, SessionID = session.SessionId, UserID = session.UserName, State = session.ConnectionState, ConnectedTime = session.ConnectTime, LogonTime = session.LoginTime, IdleTime = session.IdleTime, UserIP = session.ClientIPAddress, Workstation = session.WindowStationName });
}
srv.Close();
}
catch (Win32Exception) { }
catch (SystemException) { }
catch (Exception) { }
}
}
return res;
}
/// <summary>
/// Gets the data.
/// </summary>
/// <value>
/// The data.
/// </value>
public ObservableCollection<RemoteDataViewModel> Data { get { return remote; } }
public ObservableCollection<string> hosts { get; set; }
}
The RemoteSource is setup but a button event that does the following
DataContext = new RemoteSource();
I want to read through a text file that has the list of server names like so
Server1
Server2
Server3
etc
and load them into an ObservableCollection then be able to do the same thing I am currently doing on this line
foreach (var host in _servers) # but where _servers is the observablecollection initiated from the button event
I attempted doing something like this under the button event, but rs.hosts always returns as null
RemoteSource rs = new RemoteSource();
rs.hosts.Add(Environment.MachineName);
Your ObservableCollection<T> should be a property of your ViewModel. Then, in the View, you bind some ItemsControl.ItemsSource property to it.
For exemple (super simplified):
public class SessionViewModel : INotifyPropertyChanged
{
// ...
public ObservableCollection<String> ServerList { get; set; }
}
And in the View
<ListView x:Name="ServerList" ItemsSource="{Binding ServerList}"/>
Not sure if you're trying to let the user select a server, or edit the server. I'm answering both. Editing first, then selecting after.
Bindings can only update properties of a class. They cannot replace an instance of one type with a completely different instance of another type within a collection. That's just not how it works. Remember, this is Model View ViewModel. Your ViewModels must expose Models whose properties are bound to elements in the UI. These properties will be updated by the bindings.
So, create a Model for your server
public sealed class ServerInfo
{
public string Name {get;set;}
public string IP {get;set;}
public string Whatevs {get;set;}
}
In your VM, you would expose your list of servers from the ViewModel. If you're looking to do work on selected servers, you'd want to have a Selected property and do work on update.
public sealed class ViewModelLol : INotifyPropertyChanged
{
// init from ctor
public ObservableCollection<ServerInfo> Servers {get;private set;}
public ServerInfo SelectedServer {get;set;} // should be INPC impl, but yawn
// boring stuff goes here
}
In your UI, you'd bind an ItemsSource to the collection
<ListBox ItemsSource="{Binding Servers}" SelectedItem="{Binding SelectedServer}" >
<ListBox.ItemTemplate>
<DataTemplate>
<!-- If you wanted to edit the server name... -->
<TextBox Text="{Binding Name}"/>
<!-- If you only care about selection... -->
<Label Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Not exactly sure if you want to be able to edit the server name, if so, use the first option. If you want to present to the user a list of servers and allow them to select one, then use the label option.
Once the user selects a server, SelectedServer in the ViewModel is updated. You can take that opportunity to do whatever work you need to.
ViewModels should be at the top of the logic food chain. They interpret actions of users of the system and convert them into API calls within. So if you need to connect to a server, then the VM should contain the logic to connect to the server. The VM shouldn't be a child of some business logic class. That requires some tricky spaghetti code and will be harder to implement.
VMs are supposed to sit between the UI and your core business logic, which shouldn't care about the UI at all. For instance, connecting to a server has nothing to do with the UI. Identifying which server to connect to does. Bridging that gap is the role of the VM.

My ViewModel does not provide current variable value

[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.

WPF controls are not populated from custom method

In my WPF C# project I have two frames in the MainWindow. The first frame has a page with a DataGrid (bound to an XML file) in which I select an object of interest.
<Grid.Resources>
<XmlDataProvider x:Key="XmlData" Source="/DB.xml"/>
</Grid.Resources>
<DataGrid Name="dg"
SelectionChanged="dg_SelectionChanged"
AutoGenerateColumns="False"
ItemsSource="{Binding Source={StaticResource XmlData}, XPath=Data/Object}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding XPath=Type}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding XPath=Number}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
In the second frame I open different pages (one at a time) according to the calculations I am going to perform with the selected object. At every SelectionChanged event a custom method MySub() is called, that initiates all the necessary calculations on the loaded page.
public partial class pg_DB : Page
{
public pg_DB()
{
InitializeComponent();
}
public void dg_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (Var._loadedPage) // This variable holds the name of the loaded page.
{
case "pg_SCT":
pg_SCT c1 = new pg_SCT();
c1.MySub(); // Initiates the calculation process on pg_SCT page.
break;
case "pg_OCT":
pg_OCT c2 = new pg_OCT();
c2.MySub(); // Initiates the calculation process on pg_OCT page.
break;
}
}
}
The problem is that everything works well except the data visualization. Thus, for instance, every time the MySub() is called the List<> is being updated and the ItemsSource has the necessary items, yet they are not displayed in the DataGrid. Moreover, even simple TextBox1.Text = "Test" is not working. At the same time the same code works perfectly from the Button_Click method.
public partial class pg_SCT : Page
{
public pg_SCT()
{
InitializeComponent();
//grid.ItemsSource = myList (); (This works).
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//grid.ItemsSource = myList (); (This works).
//TextBox1.Text = "Test"; (This works).
}
public void MySub()
{
grid.ItemsSource = myList(); // Nothing happens (although debugging shows that List is updated and ItemsSource has necessary items).
TextBox1.Text = "Test"; // Textbox remains empty.
}
public class Author
{
public int ID { get; set; }
public string Name { get; set; }
}
private List<Author> myList()
{
List<Author> authors = new List<Author>();
authors.Add(new Author()
{
ID = Var._ID,
Name = Var._Name,
});
return authors;
}
}
I can’t find what is missing to populate DataGrid and TextBox from my custom method MySub().
Thank you for your time and consideration.
In your dg_SelectionChanged method, you're creating instances of your Page controls as local variables but not using them anywhere so they are just going out of scope. I'm guessing you probably have other instances you've created elsewhere that are the ones being displayed but based on the code here the ones calling MySub are never going to show up.

WPF DataBinding ObservableCollection<T> to DataGrid

I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)

Categories