Xamarin list of RadioBox set GroupName from view model - c#

All the examples for RadioButtons I've found so far have the values hard-coded in the xaml for the page. I'm looking to feed a list of strings (for now) from a database and running into an issue with the GroupName.
My data template for a radio display type:
<DataTemplate x:Key="RadioTemplate">
<StackLayout>
<Label
Text="{Binding Name}"
Style="{StaticResource LabelStyle}">
</Label>
<ListView ItemsSource="{Binding Choices.Choices}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<RadioButton Value="{Binding}"
Content="{Binding}"
GroupName="someGroupName"
CheckedChanged="OnRadioChanged">
</RadioButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</DataTemplate>
MainPageViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using FieldDataTemplateSelectorSample.Validations;
namespace FieldDataTemplateSelectorSample
{
public class MainPageViewModel : ValidationViewModelBase
{
public MainPageViewModel()
{
_fields = new ObservableCollection<FieldViewModel>();
}
private ObservableCollection<FieldViewModel> _fields;
public ObservableCollection<FieldViewModel> Fields
{
get { return _fields; }
set
{
_fields = value;
RaisePropertyChanged();
}
}
private bool _showValidationSummary;
public bool ShowValidationSummary
{
get { return _showValidationSummary; }
set
{
_showValidationSummary = value;
RaisePropertyChanged();
}
}
ICommand _submitCommand;
public ICommand SubmitCommand
{
get => _submitCommand ?? (_submitCommand = new Command(ValidateAndSave));
}
private void ValidateAndSave(object obj)
{
ValidateAll();
if (ErrorStateManager.HasError)
{
ShowValidationSummary = true;
}
else
{
ShowValidationSummary = false;
}
}
}
}
using System.Collections.Generic;
using Xamarin.Forms;
using FieldDataTemplateSelectorSample.Validations;
namespace FieldDataTemplateSelectorSample
{
public class FieldViewModel : ValidationViewModelBase
{
public FieldViewModel()
{
_choices = new ChoiceViewModel();
}
private string _value;
public long Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Code { get; set; }
public string Value
{
get
{
return _value;
}
set
{
_value = value;
RaisePropertyChanged();
Validate();
}
}
public string PlaceholderText { get; set; }
public int SortOrder { get; set; }
public string DisplayType { get; set; }
public bool IsReadOnly { get; set; }
public bool IsEnabled { get; set; }
private ChoiceViewModel _choices;
public ChoiceViewModel Choices
{
get
{
return _choices;
}
set
{
_choices = value;
RaisePropertyChanged();
}
}
void OnChoicesRadioButtonCheckedChanged(object sender, CheckedChangedEventArgs e)
{
// what else do we need to do? Update value?
RaisePropertyChanged();
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace FieldDataTemplateSelectorSample
{
public class ChoiceViewModel
{
public ChoiceViewModel()
{
Choices = new List<string>();
}
public string Code { get; set; }
public List<string> Choices { get; set; }
}
}
As long as I hard-code the GroupName for the RadioButton in the ViewCell, everything works but that I'll end up with one group name for every single radio button on the page. I've tried adding the property to the StackLayout in the template:
<StackLayout RadioButtonGroup.GroupName="someGroup">
and taking it out out of the RadioButton but when I put a breakpoint in OnRadioChanged, the GroupName comes in as null.
I'd like to use the Code property in the ChoiceViewModel as the group name but haven't gotten the relative binding correct yet. Any help with the binding or different way to do the DataTemplate is appreciated.

you could use a bindable StackLayout which will give you more control over the containter
<StackLayout>
<Label Text="{Binding Name}" Style="{StaticResource LabelStyle}" />
<StackLayout RadioButtonGroup.GroupName="{Binding Name}"
BindableLayout.ItemsSource="{Binding Choices.Choices}" >
<BindableLayout.ItemTemplate>
<DataTemplate>
<ViewCell>
<RadioButton Value="{Binding}" Content="{Binding}"
CheckedChanged="OnRadioChanged" />
</ViewCell>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>

Related

Grouped Collection View list not displaying, possible Binding error

I just began working with the MVVM layout and I cannot seem to display anything in my collectionView List. I believe it's my binding, unfortunately I don't really understand how I am supposed to bind a grouped list + the ViewModel. I read to bind to the path no the source, but I'm pretty sure I am doing this incorrectly. I have checked to see if I am even getting shares to load and I am, they're just not displaying.
Model -- Share
[JsonProperty("iSpottedID")]
public int ID { get; set; }
[JsonProperty("sShoppingList")]
[MaxLength(255)]
public string ShoppingName { get; set; }
[JsonProperty("dtInfoUpdate")]
[MaxLength(20)]
public string CreateDate { get; set; }
[JsonProperty("iProductID")]
public int ProductID { get; set; }
[Indexed]
[JsonProperty("sLocation")]
[MaxLength(255)]
public string LocationName { get; set; }
[JsonProperty("tvCardJson")]
public string JsonString { get; set; }
ViewModel -- SharesViewModel
public class SharesViewModel : BaseViewModel
{
#region Properties
private int _id;
public int ID
{
get { return _id; }
set
{
SetValue(ref _id, value);
OnPropertyChanged(nameof(ID));
}
}
private string _longName;
public string LongName
{
get { return _longName; }
set
{
SetValue(ref _longName, value);
OnPropertyChanged(nameof(LongName));
}
}
private string _date;
public string CreateDate
{
get{ return _date;}
set
{
SetValue(ref _date, value);
OnPropertyChanged(nameof(CreateDate));
}
}
private int _prodID;
public int ProductID
{
get { return _id; }
set
{
SetValue(ref _prodID, value);
OnPropertyChanged(nameof(ProductID));
}
}
private string _json;
public string JsonString
{
get { return _json; }
set
{
SetValue(ref _json, value);
OnPropertyChanged(nameof(JsonString));
}
}
private string _location;
public string LocationName
{
get { return _location; }
set
{
SetValue(ref _location, value);
OnPropertyChanged(nameof(LocationName));
}
}
//ADD-ONS
public string Address
{
get
{
if (!string.IsNullOrEmpty(JsonString))
{
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonString);
if (jsonDict.ContainsKey("address"))
if (jsonDict["address"] != "")
return jsonDict["address"];
}
return null;
}
}
private ImageSource _imageLink;
public ImageSource ImageLink
{
get
{
if(ProductID != 0)
{
...
return ImageSource.FromUri(link);
}
return null;
}
}
#endregion
public SharesViewModel(){}
public SharesViewModel(Share share)
{
ID = share.ID;
ProductID = share.ProductID;
JsonString = share.JsonString;
CreateDate = share.CreateDate;
LocationName = share.LocationName;
}
List View Model -- SharesListViewlModel
public class SharesListViewModel : BaseViewModel
{
private SharesViewModel _selectedShare;
private bool _isDataLoaded;
//grouped list
public ObservableCollection<LocationSpotGroups<string, SharesViewModel>> Shares { get; set; }
...
public ICommand OpenMoreSharesCommand { get; private set; }
public ICommand LoadDataCommand { get; private set; }
public SharesListViewModel(Position NW , Position SE)
{
_nw = NW;
_se = SE;
LoadDataCommand = new Command(async () => await LoadData());
OpenMoreSharesCommand = new Command<SharesViewModel>(async (share) => await OpenMoreShares(share));
public ObservableCollection<SharesViewModel> sList { get; set; }
= new ObservableCollection<SharesViewModel>();
}
private async Task LoadData()
{
if (_isDataLoaded)
return;
var list = await _connection.GetAllRegionShares(_nw, _se);
foreach (var spot in list)
{
sList.Add(new SharesViewModel(spot));
}
var sorted = from item in sList
orderby item.LocationName
group item by item.LocationName into itemGroup
select new LocationSpotGroups<string, SharesViewModel>(itemGroup.Key, itemGroup);
Shares = new ObservableCollection<LocationSpotGroups<string, SharesViewModel>>(sorted);
}
LocationSpotGroups
public class LocationSpotGroups<K, T> : ObservableCollection<T>
{
public K GroupKey { get; set; }
public IEnumerable<T> GroupedItem { get; set; }
public LocationSpotGroups(K key, IEnumerable<T> shares)
{
GroupKey = key;
GroupedItem = shares;
foreach (var item in shares)
{
this.Items.Add(item);
}
}
}
SharesPage XAML
<CollectionView x:Name="CollectionList"
VerticalOptions="FillAndExpand"
ItemsSource="{Binding Shares}"
IsGrouped="True">
<!--HEADER-->
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal"
Padding="5"
BackgroundColor="#f7f7fb">
<Label x:Name="labelname"
Text="{Binding GroupKey}"
HorizontalOptions="Start"
VerticalOptions="Center"
TextColor="gray" />
</StackLayout>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="2" />
</CollectionView.ItemsLayout>
<!--BODY-->
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="5" Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ImageButton Source="{Binding ImageLink}"
WidthRequest="150"
HeightRequest="150"
Grid.ColumnSpan="2"
CornerRadius="15"
Aspect="AspectFill"
Grid.Row="0"
Grid.Column="0"/>
<Label Text="{Binding ShoppingName}"
Grid.Row="1"
Grid.Column="0"/>
<Label Text="More"
Grid.Row="1"
Grid.Column="1"
HorizontalTextAlignment="End"/>
<Label Text="{Binding CreateDate}"
Grid.Row="2"
Grid.Column="0"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
SharesPage CS
public SharesPage( Position NW, Position SE )
{
InitializeComponent();
ViewModel = new SharesListViewModel(NW, SE);
}
public SharesListViewModel ViewModel
{
get { return BindingContext as SharesListViewModel; }
set { BindingContext = value; }
}
protected override void OnAppearing()
{
ViewModel.LoadDataCommand.Execute(null);
base.OnAppearing();
}
Loading the data in the constructor works if the data is not a lot, which is wasn't in my case. Everything loads perfectly.

WPF, Caliburn.Micro and Dapper ComboBoxes

I am just new to WPF, Caliburn.Micro and Dapper. I have three combo boxes: the first one is for the region, the second one is for the provinces in the particular selected region and the third one are the cities in the particular selected province. What I want to achieve is that when I selected a particular region it will display all the provinces in that region, the same with the province combo box, when selected it will display all the cities associated with that province. Can this be done in a single method? Here is my code so far.
DataAccess
public List<RegionModel> GetRegion_All()
{
List<RegionModel> output;
using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(GlobalConfig.CnnString(db)))
{
output = connection.Query<RegionModel>("dbo.spRegion_GetAll").ToList();
var p = new DynamicParameters();
foreach (RegionModel region in output)
{
p = new DynamicParameters();
p.Add("#RegionId", region.Id);
region.Provinces = connection.Query<ProvinceModel>("dbo.spProvince_ByRegion", p, commandType: CommandType.StoredProcedure).ToList();
foreach (ProvinceModel province in region.Provinces)
{
p = new DynamicParameters();
p.Add("#ProvinceId", province.Id);
region.Cities = connection.Query<CityModel>("dbo.spCity_ByProvince", p, commandType: CommandType.StoredProcedure).ToList();
}
}
}
return output;
}
Models
public class RegionModel
{
public int Id { get; set; }
public string Region { get; set; }
public string RegionName { get; set; }
public List<ProvinceModel> Provinces { get; set; } = new List<ProvinceModel>();
public List<CityModel> Cities { get; set; } = new List<CityModel>();
public List<BarangayModel> Barangays { get; set; } = new List<BarangayModel>();
}
public class ProvinceModel
{
public int Id { get; set; }
public string Province { get; set; }
public int RegionId { get; set; }
}
public class CityModel
{
public int Id { get; set; }
public string City { get; set; }
public int ProvinceId { get; set; }
public int ZipCode { get; set; }
}
ViewModel
public class ShellViewModel : Screen
{
private BindableCollection<RegionModel> _region;
private RegionModel _selectedRegion;
private ProvinceModel _selectedProvince;
public ShellViewModel()
{
GlobalConfig.InitializeConnections(DatabaseType.Sql);
Region = new BindableCollection<RegionModel>(GlobalConfig.Connection.GetRegion_All());
}
public BindableCollection<RegionModel> Region
{
get { return _region; }
set
{
_region = value;
}
}
public RegionModel SelectedRegion
{
get { return _selectedRegion; }
set
{
_selectedRegion = value;
NotifyOfPropertyChange(() => SelectedRegion);
}
}
public ProvinceModel SelectedProvince
{
get { return _selectedProvince; }
set
{
_selectedProvince = value;
NotifyOfPropertyChange(() => SelectedRegion);
}
}
View
<Window x:Class="WPFUI.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFUI.Views"
mc:Ignorable="d" WindowStartupLocation="CenterScreen"
Title="ShellView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" x:Name="Region" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="1" x:Name="SelectedRegion_Provinces" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Province}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="2" x:Name="SelectedRegion_Cities" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding City}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Most of my codes ideas are from the tutorials I found in youtube, since references and materials for WPF, Caliburn.Micro and Dapper are very hard to find. Please be patient with my code :)
You have lot of mistake and you dont use the power of Caliburn
The wpf definition:
with Caliburn if you give name Region for combobox, it waits a bindableCollection with same name (Region) and SelectedItem is named SelectedRegion
(see name convention with Caliburn). So i choose Region, Province & City
In the different models i have renamed all strings RegionName, ProvinceName and CityName.
<ComboBox Grid.Row="0" x:Name="Region" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="1" x:Name="Province" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ProvinceName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Grid.Row="2" x:Name="City" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CityName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
then, i have modified your class definition:
public class RegionModel:PropertyChangedBase
{
public int Id { get; set; }
public string Region { get; set; }
public string RegionName { get; set; }
// each Region has its Provinces
public List<ProvinceModel> Provinces { get; set; } = new List<ProvinceModel>();
}
public class ProvinceModel:PropertyChangedBase
{
public int Id { get; set; }
public string ProvinceName { get; set; }
public int RegionId { get; set; }
// each Province has its Cities
public List<CityModel> Cities { get; set; } = new List<CityModel>();
}
public class CityModel:PropertyChangedBase
{
public int Id { get; set; }
public string CityName { get; set; }
public int ProvinceId { get; set; }
public int ZipCode { get; set; }
}
Then in ViewModel i add the different BindableCollection Region, Province & City dont forget to add
using Caliburn.Micro;
in the using definion. Then Add the Selected Definition
public class ShellViewModel : Screen
{
private RegionModel selectedRegion;
private ProvinceModel selectedProvince;
private CityModel selectedCity;
private BindableCollection<RegionModel> _region;
private BindableCollection<ProvinceModel> _province;
private BindableCollection<CityModel> _city;
public BindableCollection<RegionModel> Region
{
get { return _region; }
set
{
_region = value;
NotifyOfPropertyChange(() => Region);
}
}
public BindableCollection<ProvinceModel> Province
{
get { return _province; }
set
{
_province = value;
NotifyOfPropertyChange(() => Province);
}
}
public BindableCollection<CityModel> City
{
get { return _city; }
set
{
_city = value;
NotifyOfPropertyChange(() => City);
}
}
public RegionModel SelectedRegion
{
get { return selectedRegion; }
set
{
selectedRegion = value;
NotifyOfPropertyChange(() => SelectedRegion);
Province.Clear();
Province.AddRange(selectedRegion.Provinces);
NotifyOfPropertyChange(() => Province);
}
}
public ProvinceModel SelectedProvince
{
get { return selectedProvince; }
set
{
selectedProvince = value;
NotifyOfPropertyChange(() => SelectedProvince);
City.Clear();
City.AddRange(selectedProvince.Cities);
NotifyOfPropertyChange(() => City);
}
}
public CityModel SelectedCity
{
get { return selectedCity; }
set
{
selectedCity = value;
NotifyOfPropertyChange(() => SelectedCity);
}
}
public ShellViewModel()
{
// to DO INITIALIZE Regions
//
Province = new BindableCollection<ProvinceModel>();
City = new BindableCollection<CityModel>();
Region = new BindableCollection<RegionModel>(Regions);
}
and you have a functional sample
Each time you select a region, the associated provinces are loaded and same thing if you select a province for the associated Cities

Binding a TreeView to a collection with different types of objects on same level

I want to bind my Treeview control to a collection that holds objects of type MeasurementResult. The MeasurementResult object itself has two collections, one for MeasurementInfo and one for DeviceInfo types.
After googling and searching on SO I found out that the best solution might be a CompositeCollection. The problem I have with that is that I just can't figure out how to define the (Hierarchical?!)DataTemplate's in a way that my data get shown in the Treeview in the way I want it.
Ultimately I would like to have a TreeView structure like that:
-MeasurementResult1
---MeasurementInformation
------MeasurementInformation1
------MeasurementInformation2
------MeasurementInformation3
---DeviceInformation
------DeviceInformation1
------DeviceInformation2
------DeviceInformation3
-MeasurementResult2
---MeasurementInformation
------MeasurementInformation1
------MeasurementInformation2
------MeasurementInformation3
---DeviceInformation
------DeviceInformation1
------DeviceInformation2
------DeviceInformation3
-MeasurementResultN
But the problem is that my current Treeview looks like that:
The nested properties for MeasurementData and DeviceData are not shown in my TreeView.
The code that I have so far, XAML:
<local:TreeViewSampleData x:Key="TreeViewSampleData"/>
<DataTemplate x:Key="MeasurementDataTemplate">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finished: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Finished}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Median: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Median}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Maximum: " Margin="0,0,10,0"/>
<TextBlock Text="{Binding Maximum}" />
</StackPanel>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="DeviceDataTemplate" DataType="{x:Type local:DeviceData}" ItemTemplate="{StaticResource MeasurementDataTemplate}"
ItemsSource="{Binding MeasurementData}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="MeasurementResultTemplate" DataType="{x:Type local:MeasurementResult}" ItemTemplate="{StaticResource DeviceDataTemplate}"
ItemsSource="{Binding Measurements}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<telerik:RadTreeView x:Name="tvMeasResults"
ItemsSource="{Binding Source={StaticResource TreeViewSampleData}, Path = MeasurementResults}"
ItemTemplate="{StaticResource MeasurementResultTemplate}"
>
</telerik:RadTreeView>
My related classes:
public class MeasurementResult
{
public string Name { get; set; } = "Measurement Result";
internal ObservableCollection<MeasurementInfo> MeasurementInfo { get; set; }
internal ObservableCollection<DeviceInfo> DeviceInfo { get; set; }
public CompositeCollection Measurements
{
get
{
var items = new CompositeCollection();
items.Add(new CollectionContainer { Collection = MeasurementInfo });
items.Add(new CollectionContainer { Collection = DeviceInfo });
return items;
}
}
public MeasurementResult()
{
MeasurementInfo = new ObservableCollection<MeasurementInfo>();
DeviceInfo = new ObservableCollection<DeviceInfo>();
}
}
public class MeasurementInfo
{
public string Name { get; set; } = "Measurement Information";
public ObservableCollection<MeasurementData> ThicknessData { get; set; }
public MeasurementInfo()
{
ThicknessData = new ObservableCollection<MeasurementData>();
}
}
public class MeasurementData
{
public DateTime Finished { internal set; get; }
public double Median { internal set; get; }
public double Maximum { internal set; get; }
public MeasurementData()
{
Finished = DateTime.Now;
Median = 150;
Maximum = 200;
}
}
public class DeviceInfo
{
public string Name { get; set; } = "Device Information";
public ObservableCollection<DeviceData> DeviceData { get; set; }
public DeviceInfo()
{
DeviceData = new ObservableCollection<DeviceData>();
}
}
public class DeviceData
{
public DateTime Finished { internal set; get; }
public int Port { internal set; get; }
public int Slot { internal set; get; }
public DeviceData()
{
Finished = DateTime.Now;
Port = 1;
Slot = 1;
}
}
What is wrong with my bindings? I guess the DataTemplates are wrong but I couldn't figure out how to define them to get my expected result.
This will allow you to add specific items to specific leaves and they will be concatenated by GetEnumerator so the TreeView presents things in the way you expected.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var item = new InformationTreeItem("ROOT")
{
Children =
{
new InformationTreeItem("Level 1")
{
DeviceInformation =
{
new DeviceInformation("Device 1/1"),
new DeviceInformation("Device 1/2")
},
MeasurementInformation =
{
new MeasurementInformation("Measure 1/1"),
new MeasurementInformation("Measure 1/2")
},
Children =
{
new InformationTreeItem("Level 2")
{
DeviceInformation =
{
new DeviceInformation("Device 2/1"),
new DeviceInformation("Device 2/2")
},
MeasurementInformation =
{
new MeasurementInformation("Measure 2/1"),
new MeasurementInformation("Measure 2/2")
},
Children =
{
new InformationTreeItem("Level 3")
}
}
}
}
}
};
DataContext = item;
}
}
public interface IInformation
{
string Description { get; }
}
public class InformationTreeItem : IEnumerable<IInformation>, IInformation
{
public InformationTreeItem(string description)
{
Description = description;
}
private InformationTreeItem(string description, IList<IInformation> children)
{
Description = description;
Children = children;
}
public IList<IInformation> Children { get; } = new List<IInformation>();
public IList<DeviceInformation> DeviceInformation { get; } = new List<DeviceInformation>();
public IList<MeasurementInformation> MeasurementInformation { get; } = new List<MeasurementInformation>();
public string Description { get; }
public IEnumerator<IInformation> GetEnumerator()
{
var list = new List<IInformation>();
if (DeviceInformation.Any())
{
list.Add(new InformationTreeItem(nameof(DeviceInformation), new List<IInformation>(DeviceInformation)));
}
if (MeasurementInformation.Any())
{
list.Add(new InformationTreeItem(nameof(MeasurementInformation), new List<IInformation>(MeasurementInformation)));
}
foreach (var child in Children)
{
list.Add(child);
}
return list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
return Description;
}
}
public class DeviceInformation : IInformation
{
public DeviceInformation(string description)
{
Description = description;
}
public string Description { get; }
public override string ToString()
{
return Description;
}
}
public class MeasurementInformation : IInformation
{
public MeasurementInformation(string description)
{
Description = description;
}
public string Description { get; }
public override string ToString()
{
return Description;
}
}
}

Xamarin forms ListView item dynamic update to GUI

I've created a ListView in Xamarin form and bind to Observable collection in view model, adding item dynamically to ListView is working fine by calling OnPropertyChanged event.
But after getting status update from service I'm updating corresponding ListView item status and calling OnPropertyChanged event as well as re-assigining the ListView items to it but didn't get updated GUI properly sometimes working and some times not.
Below is the sample code that I've done.
<ListView Grid.Row="3" HasUnevenRows="True" ItemsSource="{Binding ServiceList}" IsPullToRefreshEnabled="True" SeparatorColor="Black">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Spacing="4" Padding="5" BackgroundColor="LightGray">
<Label Text="{Binding OperationStatus, Converter={x:Static local:StatusMessageConverter.Default}}" FontSize="13" FontAttributes="Bold" TextColor="White" BackgroundColor="DarkCyan" />
<Label Text="{Binding Operation}" FontSize="10" Margin="10,0,0,0" />
<Label Text="{Binding OperationType}" FontSize="10" Margin="10,0,0,0" />
<Label Text="{Binding OperationStatus}" LineBreakMode="WordWrap" IsVisible="{Binding CanStatusVisible}" FontSize="10" Margin="10,0,0,0" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class ServiceViewModel : INotifyPropertyChanged
{
public ObservableCollection<ServiceItem> ServiceList
{
get
{
return _serviceList;
}
set
{
_serviceList = value;
OnPropertyChanged("ServiceList");
}
}
var tempList = new ObservableCollection<ServiceItem>();
tempList = ServiceList;
var targetItem = from item in tempList
where item.UniqueId == uniqueId
select item;
if (targetItem.Any())
{
var resultItem = targetItem.FirstOrDefault();
resultItem.CanStatusVisible = true;
resultItem.OperationStatus = string.Format("{0}: {1}", "Status Message", resultMessage);
}
ServiceList = null;
ServiceList = tempList;
OnPropertyChanged("ServiceList");
}
public class ServiceItem
{
public string UniqueId { get; set; }
public string Operation { get; set; }
public string OperationType { get; set; }
public string OperationStatus { get; set; }
public string StatusMessage { get; set; }
public bool CanStatusVisible { get; set; }
}
See to it that your model class inherits from INotifyPropertyChangedinterface(as mentioned in the above comments).
public class ServiceItem :INotifyPropertyChanged
{
private string uniqueId,operation,operationType,operationStatus,statusMessage;
private bool statusVisible;
public string UniqueId { get { return uniqueId; } set { uniqueId= value; RaisePropertyChanged(nameof(UniqueId)); } }
public string Operation { get { return operation; } set { operation= value; RaisePropertyChanged(nameof(Operation)); } }
public string OperationType { get { return operationType; } set { operationType= value; RaisePropertyChanged(nameof(OperationType)); } }
public string OperationStatus { get { return operationStatus; } set { operationStatus= value; RaisePropertyChanged(nameof(OperationStatus)); } }
public string StatusMessage { get { return statusMessage; } set { statusMessage= value; RaisePropertyChanged(nameof(StatusMessage)); } }
public bool CanStatusVisible { get { return statusVisible; } set { statusVisible= value; RaisePropertyChanged(nameof(CanStatusVisible )); } }
}
Then your ViewModel code should look something like this:
var tempList = new ObservableCollection<ServiceItem>();
tempList = ServiceList;
var targetItem = from item in tempList
where item.UniqueId == uniqueId
select item;
if (targetItem.Any())
{
var resultItem = targetItem.FirstOrDefault();
resultItem.CanStatusVisible = true;
resultItem.OperationStatus = string.Format("{0}: {1}", "Status Message", resultMessage);
}
ServiceList = null;
ServiceList = tempList;
Once you do these changes your code should work
--- To clarify my comment on FreakyAli's good answer ---
The essential part of FreakyAli's answer is the first code snippet:
public class ServiceItem :INotifyPropertyChanged
...
Once that is done, the other code in question can be greatly simplified. I think (though I have not tested) that you can replace all the code Ali shows under "Then your ViewModel code should look something like this:" with:
ServiceItem resultItem = ServiceList.Where(item => item.UniqueId == uniqueId).FirstOrDefault();
if (resultItem != null)
{
resultItem.CanStatusVisible = true;
resultItem.OperationStatus = string.Format("{0}: {1}", "Status Message", resultMessage);
}
That is, it is not necessary to create a temp list, nor to manipulate ServiceList. When you change the property of a ServiceItem, that property's RaisePropertyChanged will trigger the needed display refresh.

WPF MVVM hierarchy selected item

I am currently implementing the application that displays hierarchy using ListBoxes (please do not suggest using TreeView, ListBoxes are needed).
It looks like that in the article: WPF’s CollectionViewSource (with source code).
Classes:
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
}
public class Lift
{
public ObservableCollection<string> Runs { get; }
}
The example uses CollectionViewSource instances (see XAML) to simplify the design.
An instance of Mountains class is the DataContext for the window.
The problem is: I would like that the Mountains class to have SelectedRun property and it should be set to currently selected run.
public class Mountains : ObservableCollection<Mountain>
{
public ObservableCollection<Lift> Lifts { get; }
public string Name { get; }
public string SelectedRun { get; set; }
}
Maybe I've missed something basic principle, but how can I achieve this?
You may want to read about the use of '/' in bindings. See the section 'current item pointers' on this MSDN article.
Here's my solution:
Xaml
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="0" Text="Mountains"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="1" Text="Lifts"/>
<TextBlock Margin="5" Grid.Row="0" Grid.Column="2" Text="Runs"/>
<ListBox Grid.Row="1" Grid.Column="0" Margin="5"
ItemsSource="{Binding Mountains}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True" />
<ListBox Grid.Row="1" Grid.Column="1" Margin="5"
ItemsSource="{Binding Mountains/Lifts}" DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"/>
<ListBox Grid.Row="1" Grid.Column="2" Margin="5"
ItemsSource="{Binding Mountains/Lifts/Runs}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedRun}"/>
</Grid>
C# (note, you don't need to implement INotifyPropertyChanged unless the properties will be changed and not just selected)
public class MountainsViewModel
{
public MountainsViewModel()
{
Mountains = new ObservableCollection<Mountain>
{
new Mountain
{
Name = "Whistler",
Lifts = new ObservableCollection<Lift>
{
new Lift
{
Name = "Big Red",
Runs = new ObservableCollection<string>
{
"Headwall",
"Fisheye",
"Jimmy's"
}
},
new Lift
{
Name = "Garbanzo",
Runs = new ObservableCollection<string>
{
"Headwall1",
"Fisheye1",
"Jimmy's1"
}
},
new Lift {Name = "Orange"},
}
},
new Mountain
{
Name = "Stevens",
Lifts = new ObservableCollection<Lift>
{
new Lift {Name = "One"},
new Lift {Name = "Two"},
new Lift {Name = "Three"},
}
},
new Mountain {Name = "Crystal"},
};
}
public string Name { get; set; }
private string _selectedRun;
public string SelectedRun
{
get { return _selectedRun; }
set
{
Debug.WriteLine(value);
_selectedRun = value;
}
}
public ObservableCollection<Mountain> Mountains { get; set; }
}
public class Mountain
{
public string Name { get; set; }
public ObservableCollection<Lift> Lifts { get; set; }
}
public class Lift
{
public string Name { get; set; }
public ObservableCollection<string> Runs { get; set; }
}
Here's how I would do it. You want to make sure that you fire the INotifyPropertyChanged event when setting the properties. To get the Selected Run you'll have to get MainViewModel.SelectedMountain.SelectedLift.SelectedRun.
public class MainViewModel: ViewModelBae
{
ObservableCollection<MountainViewModel> mountains
public ObservableCollection<MountainViewModel> Mountains
{
get { return mountains; }
set
{
if (mountains != value)
{
mountains = value;
RaisePropertyChanged("Mountains");
}
}
}
MountainViewModel selectedMountain
public MountainViewModel SelectedMountain
{
get { return selectedMountain; }
set
{
if (selectedMountain != value)
{
selectedMountain = value;
RaisePropertyChanged("SelectedMountain");
}
}
}
}
public class MountainViewModel: ViewModelBae
{
ObservableCollection<LiftViewModel> lifts
public ObservableCollection<LiftViewModel> Lifts
{
get { return lifts; }
set
{
if (lifts != value)
{
lifts = value;
RaisePropertyChanged("Lifts");
}
}
}
LiftViewModel selectedLift
public LiftViewModel SelectedLift
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
public class LiftViewModel: ViewModelBae
{
ObservableCollection<string> runs
public ObservableCollection<string> Runs
{
get { return runs; }
set
{
if (runs != value)
{
runs = value;
RaisePropertyChanged("Runs");
}
}
}
string selectedRun
public string SelectedRun
{
get { return selectedLift; }
set
{
if (selectedLift != value)
{
selectedLift = value;
RaisePropertyChanged("SelectedLift");
}
}
}
}
<ListBox ItemsSource="{Binding Mountains}" SelectedItem="{Binding SelectedMountain, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.Lifts}" SelectedItem="{Binding SelectedMountain.SelectedLift, Mode=TwoWay}">
<ListBox ItemsSource="{Binding SelectedMountain.SelectedLift.Runs}" SelectedItem="{Binding SelectedMountain.SelectedLift.SelectedRun, Mode=TwoWay}">
Your ViewModel should not also be a collection, it should contain collections and properties which are bound to the view. SelectedRun should be a property of this ViewModel (MountainViewModel) not Mountains. MountainViewModel should expose the Mountains collection and SelectedRun and should be bound to the listboxes' ItemsSource and SelectedItem.

Categories