TreeListView/DataGrid in .NET 5 - datagrid

I am trying to implement a TreeListView/DataGrid in .Net 5.
What I need is extendible columns (meaning I can adapt the width in the GUI) and also fold-out rows (like in a tree, each parent and subnode should have data in multiple columns).
For example like this:
What I tried so far:
TreeView: Doesn't have extendible columns
DataGrid and grouping: The header=parent does not have columns like its childrows.
I tried many useful packages like "objectListView", but they do not run on .Net5.
I downloaded DotNetProjects.Extended.Wpf.Toolkit, but don't find a good documentation on how to use it.
Which would be the best way to get ahead with this?
Thanks for any hints!

I ended up writing my own expander button.
It's just a prototype, intended to be used only with one sublevel and without column-sorting.
But it can be extended for other use cases. Hope it might help someone who bumps over the same issue.
I'm still looking forward to getting any hints or ideas for improvement.
thx!
Example outcome:
View (XAML)
<DataGrid Name="dgUsers" Grid.Row="0" Visibility="Visible" AutoGenerateColumns="False" ItemsSource="{Binding Users}"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="Expander" Width="Auto"></ColumnDefinition>
<ColumnDefinition Name="Content" Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" HorizontalAlignment="Left" Background="GhostWhite" BorderBrush="GhostWhite"
Visibility="{Binding ExpanderVisibility}"
Command="{Binding ExpandCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}"
Content="{Binding ExpanderSign}">
</Button>
<CheckBox Grid.Column="1" Margin="{Binding CheckboxMargin}"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="{Binding JobNameMargin}"/>
</Grid>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Birthday" Binding="{Binding Birthday}" />
</DataGrid.Columns>
</DataGrid>
ViewModel (C#):
public class JobViewModel : BindableBase
{
private ObservableCollection<User> _users = new ObservableCollection<User>();
private static string _isExpandedSign = "^";
private static string _expandableSign = ">";
public JobViewModel()
{
var cori = ((new User() { Name = "Cori", Birthday = new DateTime(1982, 8, 10), ExpanderVisibility = "Hidden" }));
var penny = ((new User() { Name = "Penny", Birthday = new DateTime(1976, 7, 31), ExpanderVisibility = "Hidden" }));
var john = ((new User() { Id = 10, Name = "John Doe", Birthday = new DateTime(1971, 7, 23) } ));
var samy = ((new User() { Id = 20, Name = "Sammy Doe", Birthday = new DateTime(1991, 9, 2) }));
var jane = ((new User() { Id = 30, Name = "Jane Doe", Birthday = new DateTime(1974, 1, 17) }));
var anny = ((new User() { Id = 40, Name = "Anny Doe", Birthday = new DateTime(1931, 1, 26) }));
john.AddChild(cori);
john.AddChild(penny);
jane.AddChild(cori);
jane.AddChild(penny);
anny.AddChild(cori);
anny.AddChild(penny);
_users.Add(john);
_users.Add(samy);
_users.Add(jane);
_users.Add(anny);
}
public class User : BindableBase
{
public int Id { get; set; }
public string ExpanderVisibility { get; set; }
public string CheckboxMargin { get; set; }
public string JobNameMargin { get; set; }
public bool IsExpanded { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public List<User> Children { get; set; }
public bool IsChild { get; set; }
public ICommand ExpandCommand { get; private set; }
public bool HasChildren()
{
return true;
}
public User(){
ExpandCommand = new DelegateCommand<DataGrid>(ExecuteExpandCommand);
ExpanderVisibility = "Hidden";
CheckboxMargin = "3,0,0,0";
JobNameMargin = "25,0,5,0";
Children = new List<User>();
}
public string ExpanderSign
{
get {
if (this.IsExpanded) return (_isExpandedSign);
else return (_expandableSign);
}
}
public void AddChild(User child)
{
child.CheckboxMargin = "23,0,0,0";
child.JobNameMargin = "45,0,5,0";
child.IsChild = true;
Children.Add(child);
ExpanderVisibility = "Visible";
}
private void ExecuteExpandCommand(DataGrid dg)
{
ObservableCollection<User> items = (ObservableCollection<User>)dg.ItemsSource;
int parentIndex=items.IndexOf(this);
if (!IsExpanded)
{
//expand
for (int i = 0; i < Children.Count; ++i)
{
items.Insert(parentIndex + 1 + i, Children.ElementAt(i));
}
}
else
{
int childrenCount = Children.Count;
for (int i=0; i< childrenCount; ++i)
{
if(parentIndex + 1 < items.Count && items.ElementAt(parentIndex + 1).IsChild) //just to be sure
{
items.RemoveAt(parentIndex + 1);
}
}
}
IsExpanded = !IsExpanded;
RaisePropertyChanged("ExpanderSign");
}
}
public ObservableCollection<User> Users
{
get { return _users; }
set { _users = value; }
}
}

Related

Get the value in entry field then reflect/react to a label in xamarin form

My problem is idk how to reflect in a label depending of input value in a entry field by a customer.
To make the things clear, let's start in our database.
Our database
Few information about our realtime database.
In our DELIVERY TABLE, we have 3 types of delivery(standard, reservation and express). In express, by the word itself, it's a rush delivery and we will require a DELIVERY FEE from the customer.
Another table is PRODUCT. We have 2 product for now, MINERAL(PROD1) AND SPARKLING(PROD2). The price of PROD1 is 35 ana PROD2 is 40.
What I've try right now is I put an SelectedIndexChanged in my picker delivery type and picker product type.
//This is my deliverytype event
private async void Picker_DeliveryType_SelectedIndexChanged(object sender, EventArgs e)
{
DELIVERY deliverySave = Picker_DeliveryType.SelectedItem as DELIVERY;
var selectedDeliveryItem = deliverySave.deliveryType;
var note = deliverySave.deliveryFee;
if(selectedDeliveryItem == "Express")
{
await DisplayAlert("Note", "Estimated Delivery: 2 hours from now", "OK");
labelDeliveryFee.Text = "Delivery Fee:" + note;
entryfieldReservationDate.IsEnabled = false;
}
else if(selectedDeliveryItem == "Standard")
{
await DisplayAlert("Note", "Within the day", "OK");
entryfieldReservationDate.IsEnabled = true;
}
else
{
await DisplayAlert("Note", "Enter Reservation Date", "OK");
entryfieldReservationDate.IsEnabled = true;
}
}
//This is my product type event
private void Picker_ProductType_SelectedIndexChanged(object sender, EventArgs e)
{
PRODUCT prod = Picker_ProductType.SelectedItem as PRODUCT;
var selectedProductItem = prod.productType;
var productPricing = prod.productPrice;
if (selectedProductItem == "Mineral")
{
labelProductPrice.Text = Convert.ToString(productPricing);
}
else
{
labelProductPrice.Text = Convert.ToString(productPricing);
}
}
AND my expected output is I want the 2 SelectedIndexChanged will put inside my order button.
//this is my order button click functio now
async private void Button_Clicked(object sender, EventArgs e)
{
if (selectedDeliveryType == "Standard")
{
if (selectedProductItem == "Mineral")
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
else
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
}
else if (selectedDeliveryType == "Reservation")
{
if (selectedProductItem == "Mineral")
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
else
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
}
else
{
int deliveryfee = deliverySave.deliveryFee;
if (selectedProductItem == "Mineral")
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
else
{
//some code here
waterOrder.orderTotalAmount = totalprice;
}
}
//some code here
var SaveData = await waterorderRepos.Save(waterOrder);
var SaveDataToCustomerNotification = await waterorderRepos.SaveCustomerNotification(customerNotification);
if (SaveData)
{
await this.DisplayAlert("Order", "Order successfully", "OK");
ClearData();
CloseAllPopup();
return;
}
else
{
await this.DisplayAlert("Order", "We cannot process your order at the moment.", "OK");
}
}
I will show you some visual presentation between my work now and my expected output.
This is the image.
Please help me guys, idk how to it.Also, no MVVM please cause IDK how to do it. Thank you so much.
Based on the complexity of your code, I recommend that you use the MVVM pattern for implementation.
I created a demo and achieved your function.
You can refer to the following code:
1.create a view model MyViewModel.cs
public class MyViewModel: INotifyPropertyChanged
{
public ObservableCollection<Delivery> Deliveries { get; set; }
private Delivery _deliverySelectedItem;
public Delivery DeliverySelectedItem
{
get => _deliverySelectedItem;
set {
SetProperty(ref _deliverySelectedItem, value);
// update the TotalAmount
caculateTotalAmount();
}
}
public ObservableCollection<Product> Products { get; set; }
//add SelectedItem here
private Product _productSelectedItem;
public Product ProductSelectedItem
{
get => _productSelectedItem;
set {
SetProperty(ref _productSelectedItem, value);
// update the TotalAmount
caculateTotalAmount();
}
}
private int _quantity;
public int Quantity
{
get => _quantity;
set
{
SetProperty(ref _quantity, value);
// update the TotalAmount
caculateTotalAmount();
}
}
private int _totalAmount;
public int TotalAmount
{
get => _totalAmount;
set
{
SetProperty(ref _totalAmount, value);
}
}
private void caculateTotalAmount() {
if (String.IsNullOrEmpty(Quantity.ToString() ) || Quantity == 0) {
TotalAmount = 0;
return;
}
if (ProductSelectedItem!=null && DeliverySelectedItem!=null) {
TotalAmount = ProductSelectedItem.productPrice * Quantity + DeliverySelectedItem.deliveryFee;
}
}
public MyViewModel() {
Products = new ObservableCollection<Product>();
Products.Add(new Product { ProductId = 01, productType = "Products", productPrice = 10 });
Products.Add(new Product { ProductId = 02, productType = "02", productPrice = 12 });
Products.Add(new Product { ProductId = 03, productType = "Products", productPrice = 13 });
Products.Add(new Product { ProductId = 04, productType = "Products", productPrice = 15 });
Deliveries = new ObservableCollection<Delivery>();
Deliveries.Add(new Delivery { deliveryFee = 10, deliveryType = "Express" });
Deliveries.Add(new Delivery { deliveryFee = 20, deliveryType = "Standard" });
Deliveries.Add(new Delivery { deliveryFee = 30, deliveryType = "Standard" });
}
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;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
2.create class Delivery.cs and Product.cs
public class Delivery
{
public string deliveryType { get; set; }
public int deliveryFee { get; set;}
}
public class Product
{
public int ProductId { get; set; }
public string productType { get; set; }
public int productPrice { get; set; }
}
3.MainPage.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:pickerapp2023112="clr-namespace:PickerApp2023112"
x:Class="PickerApp2023112.MainPage">
<ContentPage.BindingContext>
<pickerapp2023112:MyViewModel></pickerapp2023112:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<Picker x:Name="Picker_DeliveryType" ItemsSource="{Binding Deliveries}" ItemDisplayBinding="{Binding deliveryFee}" SelectedItem="{Binding DeliverySelectedItem}"
Title="Select a delivery type"
TitleColor="Red">
</Picker>
<Picker x:Name="Picker_ProductType" ItemsSource="{Binding Products}" ItemDisplayBinding="{Binding productPrice}" SelectedItem="{Binding ProductSelectedItem}"
Title="Select a product type"
TitleColor="Red">
</Picker>
<StackLayout Orientation="Horizontal">
<Label Text="Please input quantity: " BackgroundColor="CadetBlue"/>
<Entry Placeholder="0" Text="{Binding Quantity}" TextColor="Red" HorizontalOptions="FillAndExpand"></Entry>
</StackLayout>
<Label x:Name="labelDeliveryFee" Text="{Binding DeliverySelectedItem.deliveryFee,StringFormat='The delivery free is {0:F1}'}" HorizontalOptions="StartAndExpand" BackgroundColor="Yellow"></Label>
<Label x:Name="labelProductPrice" Text="{Binding ProductSelectedItem.productPrice,StringFormat='The product price is {0:F2}'}" HorizontalOptions="StartAndExpand" BackgroundColor="Yellow"></Label>
<StackLayout Orientation="Horizontal">
<Label Text="The total amount: " BackgroundColor="CadetBlue"/>
<Entry Placeholder="0" Text="{Binding TotalAmount}" TextColor="Red" HorizontalOptions="FillAndExpand"></Entry>
</StackLayout>
</StackLayout>
</ContentPage>
Note:
1.I add two objects for the SelectedItem property of two Pickers and implement interface INotifyPropertyChanged for this ViewModel, if we change the value of the property, the UI will update automatically. The same is true for other properties.
private Delivery _deliverySelectedItem;
public Delivery DeliverySelectedItem
{
get => _deliverySelectedItem;
set {
SetProperty(ref _deliverySelectedItem, value);
}
}
public ObservableCollection<Product> Products { get; set; }
//add SelectedItem here
private Product _productSelectedItem;
public Product ProductSelectedItem
{
get => _productSelectedItem;
set {
SetProperty(ref _productSelectedItem, value);
}
}
In this condition, we don't need add event SelectedIndexChanged for Picker.

Show CollectionView Grouping not working in Xamarin

I have an article here about showing Data Groups from Preferences . As per everyone's input I switched to CollectionView. I have consulted the article https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/grouping. This is what I have:
public class CartUser
{
public int IDProduct { get; set; }
public string NameProduct { get; set; }
public string SupplierID { get; set; }
}
SupplierIDGrouping.cs
public class SupplierIDGrouping : ObservableCollection<CartUser>
{
public string SupplierID { get; private set; }
public SupplierIDGrouping(string supplierID)
: base()
{
SupplierID = supplierID;
}
public SupplierIDGrouping(string supplierID, IEnumerable<CartUser> source)
: base(source)
{
SupplierID = supplierID;
}
}
PageOne.xaml
<CollectionView ItemsSource="{Binding SupplierList}" IsGrouped="true">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding NameProduct}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
PageOne.xaml.cs
public ObservableCollection<SupplierIDGrouping> SupplierList { get; private set; } = new ObservableCollection<SupplierIDGrouping>();
List<CartUser> cartUsers = new List<CartUser>();
var mycart = Preferences.Get("CartUserAdds", "_mycart");
var getcart = JsonConvert.DeserializeObject<List<CartUser>>(mycart);
cartUsers = getcart;
foreach (var item in cartUsers)
{
if (!SupplierList.Any(supplierid => supplierid.SupplierID == item.SupplierID))
{
SupplierList.Add(new SupplierIDGrouping(item.SupplierID));
}
SupplierList.Single(supplierid => supplierid.SupplierID== item.SupplierID).Add(item);
}
BindingContext = this;
The data I am taken from Preferences:
[{\"IDProduct\":1,\"NameProduct\":\"Name product 1\",\"SupplierID\":\"22379356\"},{\"IDProduct\":2,\"NameProduct\":\"Name product 2\",\"SupplierID\":\"22379356\"},{\"IDProduct\":3,\"NameProduct\":\"Name product 3\",\"SupplierID\":\"12336544\"}]
However my results are still not grouped by SupplierID
This is what I want:
Looking forward to everyone's help. Thank you very much!
Update
Data corresponds to 2 groups. Group 1: 2 products, group 2: 1 product
foreach (var item in cartUsers)
{
if (!SupplierList.Any(supplierid => supplierid.SupplierID == item.SupplierID))
{
SupplierList.Add(new SupplierIDGrouping(item.SupplierID));
}
SupplierList.Single(supplierid => supplierid.SupplierID== item.SupplierID).Add(item);
}
var getresult = SupplierList;
foreach(var i in getresult)
{
}
BindingContext = this;
Update 2
public class SupplierIDGrouping : ObservableCollection<CartUser>
{
public string SupplierID { get; private set; }
public string Name { get { return SupplierID; } }
public SupplierIDGrouping(string supplierID)
: base()
{
SupplierID = supplierID;
}
public SupplierIDGrouping(string supplierID, IEnumerable<CartUser> source)
: base(source)
{
SupplierID = supplierID;
}
}
PageOne.xaml
<CollectionView ItemsSource="{Binding SupplierList}" IsGrouped="true" Header="{Binding Name}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding NameProduct}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I had to provide a GroupHeaderTemplate to make it work. Not sure why - according to the docs it should not be necessary
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding SupplierID}"
BackgroundColor="LightGray"
FontSize="Large"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>

Setting list in Xamarin.Forms

I am working to build an app which will use the code behind to supply new text via bindings when you click the next button. Each time I set the page to load on the virtual phone it times out the hot reload... The worst part is that I am not getting any errors either. Any ideas?
Content Page:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="List.MainPage">
<ContentPage.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding TitleText}" />
<ScrollView VerticalOptions="FillAndExpand">
<StackLayout>
<Label Text="{Binding EngText}" />
<Label Text="{Binding ItText}" />
</StackLayout>
</ScrollView>
<Button Text="Next Page" Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage.Content>
Code Behind:
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace List
{
public partial class MainPage : ContentPage
{
List<MainPage> Contacts { get; set; }
public string TitleText { get; set; }
public string EngText { get; set; }
public string ItText { get; set; }
int ndx = 0;
public MainPage()
{
InitializeComponent();
Contacts = new List<MainPage>
{
// repeat this for as many contacts as you need
new MainPage
{
TitleText = "Title1",
EngText = "EngText1",
ItText = "ItText1"
},
new MainPage
{
TitleText = "Title2",
EngText = "EngText2",
ItText = "ItText2"
},
};
// display the first contact
BindingContext = Contacts[ndx];
}
void OnNavigateButtonClicked(object sender, EventArgs e)
{
// increment your index
ndx++;
// check that we haven't gone too far
if (ndx < Contacts.Count)
{
BindingContext = Contacts[ndx];
}
}
}
}
you are using the same class MainPage for your UI and your data. The constructor of MainPage creates 2 new instances of `MainPage, each of which call their constructor and create 2 more instances, which recurses forever until you use all the memory and crash
you need two different classes, one for your UI and one for your data
public class Data
{
public string TitleText { get; set; }
public string EngText { get; set; }
public string ItText { get; set; }
}
public partial class MainPage : ContentPage
{
List<Data> Contacts { get; set; }
int ndx = 0;
public MainPage()
{
InitializeComponent();
Contacts = new List<Data>
{
// repeat this for as many contacts as you need
new Data
{
TitleText = "Title1",
EngText = "EngText1",
ItText = "ItText1"
},
new Data
{
TitleText = "Title2",
EngText = "EngText2",
ItText = "ItText2"
},
};

Value Converter is executed before the data is updated in View Model property in Xamarin Forms

I have a very basic need, but it seems quite challenging to achieve such a simple thing in Xamarin Forms, especially when I compare it with the way the React Native let us do the same thing.
Anyways, so I am trying to highlight a frame's background color based on the selected Id. For that, I have created a value converter, and passing Id to check and convert to the desired background color.
Below is my XAML Code:
<Frame CornerRadius="6" Padding="10" Margin="5" WidthRequest="110" HeightRequest="80"
BackgroundColor="{Binding TitleId, Converter={StaticResource
selectedGuidelineToColorConverter},ConverterParameter={x:Reference Guidelines}}">
<StackLayout>
<Label Style="{StaticResource MaterialIcons}" Text="󰠆" FontSize="20"/>
<Label Text="{Binding Title}" FontSize="13" HorizontalTextAlignment="Start" TextColor="#333d47"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Path=BindingContext.SelectedGuidelineCommand, Source={x:Reference Guidelines}}" CommandParameter="{Binding}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</Frame>
Below is my Converter Code:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var page = parameter as ContentPage;
GuidelinesViewModel model = null;
if(page != null)
{
model = page.BindingContext as GuidelinesViewModel;
}
if(model != null && model.CurrentVisibleGuideline != null && model.CurrentVisibleGuideline.TitleId == (int)value)
{
return "#808080";
}
return "#fff";
}
My Model Code:
public class Guideline
{
public int TitleId { get; set; }
public string Title { get; set; }
public Section Section { get; set; }
public List<Content> Content { get; set; }
public List<QnA> QnA { get; set; }
}
Here is my View Model Code:
Guideline guideline = null;
public Guideline CurrentVisibleGuideline
{
get { return guideline; }
set { SetProperty(ref guideline, value); }
}
public ICommand SelectedGuidelineCommand
{
get
{
return new Command<Guideline>((guideline) => ExecuteSelectedGuidelineCommand(guideline));
}
}
void ExecuteSelectedGuidelineCommand(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
}
async void GetGuidelines()
{
IsBusy = true;
Guidelines = new ObservableCollection<Guideline>();
try
{
var guidelines = await DataStore.GetGuidelinesAsync(CurrentVisibleSection);
foreach (var guideline in guidelines)
{
Guidelines.Add(guideline);
}
CurrentVisibleGuideline = Guidelines[0];
TotalGuidelines = Guidelines.Count;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
"CurrentVisibleGuideline" is a property in my View Model which contains the TitleId and other details of the selected guideline.
Problem is that, the converter code is executed before the CurrentVisibleGuideline = Guidelines[0] in the view model, and therefore, I get null in CurrentVisibleGuideline in the converter.
I believe that once the data is updated in the view model upon command execution, the XAML code should re-render the view and re-run the converter, but in my case it doesn't seem to happen.
Add an IsSelected boolean to you Guideline View Model.
Update the convertor to take in a bool and return a string that will return
your Hex color value.
When you change the CurrentVisibleGuideline Set the IsSelected flag on your Guideline View Model.
ViewModel
public class Guideline
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
}
}
public int TitleId { get; set; }
public string Title { get; set; }
public Section Section { get; set; }
public List<Content> Content { get; set; }
public List<QnA> QnA { get; set; }
}
ObservableCollection<Guideline> Guidelines { get; set; }
Guideline guideline = null;
public Guideline CurrentVisibleGuideline
{
get { return guideline; }
set { SetProperty(ref guideline, value); }
}
public ICommand SelectedGuidelineCommand
{
get
{
return new Command<Guideline>((guideline) => ExecuteSelectedGuidelineCommand(guideline));
}
}
void ExecuteSelectedGuidelineCommand(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
}
async void GetGuidelines()
{
IsBusy = true;
Guidelines = new ObservableCollection<Guideline>();
try
{
var guidelines = await DataStore.GetGuidelinesAsync(CurrentVisibleSection);
foreach (var guideline in guidelines)
{
Guidelines.Add(guideline);
}
CurrentVisibleGuideline = Guidelines[0];
TotalGuidelines = Guidelines.Count;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
void SetCurrentGuideline(Guideline guideline)
{
CurrentVisibleGuideline = guideline;
foreach (var gl in Guidelines)
gl.IsSelected = gl.TitleId == CurrentVisibleGuideline.TitleId;
}
Converter
public class BooleanToStringConverter : IValueConverter
{
public string TrueValue { get; set; } = "#808080"
public string FalseValue { get; set; } = "#FFF"
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
return TrueValue;
else
return FalseValue;
}
return "";
}
}
XAML
<Frame CornerRadius="6" Padding="10" Margin="5" WidthRequest="110" HeightRequest="80"
BackgroundColor="{Binding IsSelected, Converter={StaticResource BooleanToString}}">
<StackLayout>
<Label Style="{StaticResource MaterialIcons}" Text="󰠆" FontSize="20"/>
<Label Text="{Binding Title}" FontSize="13" HorizontalTextAlignment="Start" TextColor="#333d47"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Path=BindingContext.SelectedGuidelineCommand, Source={x:Reference Guidelines}}" CommandParameter="{Binding}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</Frame>
On a side note you will also need to turn the guideline(s) you get back from your DataStore into a ViewModel version.

How to refresh data grid with new search results with MVVM Light

I'm using the latest MMVM Light windows 8 binaries and VS 2012 latest updates, so all is good there. I'm new to the MVVM Light framework, so it's an adjustment.
I have a Customers page with a grid that is searched with a textbox and button - the text box is bound and the button uses a command. The data is showing up in the view model just fine. I LINQ over the Customers List and set the Customers list property - all works well. The problem is, the page doesn't refresh. When I go to another page and return to the Customers page, the searched data is displayed.
I suspect the view model is static and needs to re-instantiated.
The follow are the respective code frags:
public partial class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
// Services
SimpleIoc.Default.Register<INavigationService, NavigationService>();
SimpleIoc.Default.Register<IMessenger, Messenger>();
// View Models
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<CustomersViewModel>();
SimpleIoc.Default.Register<CustomerViewModel>(true);
SimpleIoc.Default.Register<ContactsViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public CustomersViewModel Customers
{
get
{
return ServiceLocator.Current.GetInstance<CustomersViewModel>();
}
}
public CustomerViewModel Customer
{
get
{
return ServiceLocator.Current.GetInstance<CustomerViewModel>();
}
}
public ContactsViewModel Contacts
{
get
{
return ServiceLocator.Current.GetInstance<ContactsViewModel>();
}
}
public static void Cleanup()
{
}
}
}
public class CustomersViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private INavigationService _navigationService;
private IMessenger _messenger;
public RelayCommand<string> RefreshClickCommand { get; set; }
public RelayCommand<string> SearchCustomersCommand { get; set; }
public const string CustomersPropertyName = "Customers";
private ObservableCollection<Customer> _customers = null;
public ObservableCollection<Customer> Customers
{
get
{
return _customers;
}
set
{
if (_customers == value)
{
return;
}
_customers = value;
RaisePropertyChanging(CustomersPropertyName);
}
}
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
public string WelcomeTitle
{
get
{
return _welcomeTitle;
}
set
{
if (_welcomeTitle == value)
{
return;
}
_welcomeTitle = value;
RaisePropertyChanged(WelcomeTitlePropertyName);
}
}
public const string CustomerSearchTermPropertyName = "CustomerSearchTerm";
private string _customerSearchTerm = string.Empty;
public string CustomerSearchTerm
{
get
{
return _customerSearchTerm;
}
set
{
if (_customerSearchTerm == value)
{
return;
}
_customerSearchTerm = value;
RaisePropertyChanging(CustomerSearchTermPropertyName);
}
}
public Customer SelectedItem
{
set
{
Customer customer = value;
_messenger.Send<Customer>(customer, "Customer");
_navigationService.Navigate(typeof(CustomerPage));
}
}
public CustomersViewModel(IDataService dataService)
{
_navigationService = SimpleIoc.Default.GetInstance<INavigationService>();
_messenger = SimpleIoc.Default.GetInstance<IMessenger>();
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
if (error != null)
{
// Report error here
return;
}
WelcomeTitle = item.Title + "Customers";
});
GetCustomers();
InitializeCommands();
}
private void InitializeCommands()
{
RefreshClickCommand = new RelayCommand<string>((item) =>
{
GetCustomers();
});
SearchCustomersCommand = new RelayCommand<string>((item) =>
{
SearchCustomers();
});
}
private void GetCustomers()
{
_customers = _dataService.GetCustomers();
}
private void SearchCustomers()
{
var cust = _dataService.GetCustomers();
List<Customer> customers = (from c in cust
where c.CompanyName.StartsWith(_customerSearchTerm)
orderby c.CompanyName
select c).ToList();
_customers = new ObservableCollection<Customer>(customers);
}
}
<common:LayoutAwarePage x:Class="SalesAccountManager.Views.RelationshipManager.CustomersPage"
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:common="using:SalesAccountManager.Common"
xmlns:ignore="http://www.ignore.com"
xmlns:telerikGrid="using:Telerik.UI.Xaml.Controls.Grid"
xmlns:WinRtBehaviors="using:WinRtBehaviors"
xmlns:Win8nl_Behavior="using:Win8nl.Behaviors"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Customers, Source={StaticResource Locator}}">
....
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="Customers" FontFamily="Segoe UI" FontSize="38"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0, 0, 100, 0">
<TextBox Height="20" Width="600" Background="White" Text="{Binding CustomerSearchTerm, Mode=TwoWay}" />
<Button Background="White" Command="{Binding SearchCustomersCommand}">
<Image Source="../../Images/Search.jpg" Height="20" Width="20"></Image>
</Button>
</StackPanel>
</Grid>
Any guidance on this would be appreciated...
Thanks!

Resources