I am trying to bind a ObservableCollection on a ListView ItemsSource but it doesn't show anything to me, tried binding inside code, on the xaml..
public partial class ReadPage : ContentPage
{
private CarregarClientes _CClientes = new CarregarClientes();
private MySQLCon _db = new MySQLCon();
private MySQLConOverloads _over = new MySQLConOverloads();
private MySQLiteCon _dbSqLiteCon = new MySQLiteCon();
private MySQLiteConOverloads _oversqlite = new MySQLiteConOverloads();
//MY OBSERVABLECOLLECTION DEFINITION -----------------------------
public ObservableCollection<Clientes> _ClientesList { get; set; }
private string _tabela = "Clientes";
public ReadPage()
{
InitializeComponent();
backBtn.Clicked += async (s, o) => await Navigation.PopModalAsync();
//Method used to populate the ObservableCollection (_ClientesList);
PopularObservableCollection();
}
I am defining the ObservableCollection inside my ReadPage class, then populating it with a method called PopularObservableCollection.
public void PopularObservableCollection()
{
_ClientesList = new ObservableCollection<Clientes>();
int quantidadeDados = _CClientes.CNumeroItems() -1;
List<string> id = _CClientes.Cid();
List<string> debito = _CClientes.CDebito();
List<string> endereco = _CClientes.CEndereco();
List<string> nome = _CClientes.CNome();
List<string> observacao = _CClientes.CObservacao();
List<string> saldo = _CClientes.CSaldo();
List<string> telefone = _CClientes.CTelefone();
for (int i = 0; i <= quantidadeDados; i++)
{
_ClientesList.Add(new Clientes
{
id = id[i],
Debito = debito[i],
Endereco = endereco[i],
Nome = nome[i],
Observacao = observacao[i],
Saldo = saldo[i],
Telefone = telefone[i]
});
}
}
Clientes.cs:
public class Clientes : BindableObject
{
public string id { get; set; }
public string Nome { get; set; }
public string Endereco { get; set; }
public string Telefone { get; set; }
public string Debito { get; set; }
public string Saldo { get; set; }
public string Observacao { get; set; }
}
XAML:
<ListView
BindingContext="{Binding Source={x:Reference MyPage}, Path=.}"
x:Name="readListView"
BackgroundColor="PaleVioletRed"
HasUnevenRows="True"
CachingStrategy="RecycleElement"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding _ClientesList}"
VerticalOptions="FillAndExpand"
ItemTapped="ReadListView_OnItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label Text="Id:" Grid.Row="0"/>
<Label Text="{Binding id}" Grid.Row="0" Margin="10,0,0,0"/>
<Label Text="{Binding Nome}" Grid.Row="1" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Related
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.
I have created in my viewmodel Dictionary<string, string>.
public ObservableCollection<Dictionary<string, string>> ObjectDetailsRows { get; set; } = new ObservableCollection<Dictionary<string, string>>();
public void SomeMethod()
{
...
//add some items to a dictionary..
...
OnPropertyChanged(nameof(ObjectDetailsRows));
}
On my XAML part Im trying to do this:
<ListView
ItemsSource="{Binding Path=ObjectDetailsRows}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding Key}" Detail="{Binding Value}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I can clearly see that I binded correctly my dictionary, I can see lines on my page (in emulator).
If I remove binding for example Key and put just Text="some text" it works and populates all 26 rows, based on provided dictionary (which has 26 objects).
But when I set just like this Text="{Bindings Key}" or Text="{Bindings Value}" it gives me nothing.
UPDATE:
public class CustomPin : Pin
{
//string base64
public string PinIcon { get; set; }
public Color Color { get; set; }
public InfoBoxMapModel InfoBox { get; set; } = new InfoBoxMapModel();
public int Id { get; set; }
}
public class InfoBoxMapModel
{
//base64
public string ImageString { get; set; }
public List<string> Items { get; set; } = new List<string>();
public Dictionary<string, string> ItemsTest { get; set; } = new Dictionary<string, string>();
}
private async Task GetObjectInstancesList()
{
var objectsResponse = await ApiServiceProvider.GetObjectInstances();
Device.BeginInvokeOnMainThread(() =>
{
ListOfObjects = new ObservableCollection<CustomPin>();
if (objectsResponse.Succeeded)
{
foreach (var item in objectsResponse.ObjectInstances)
{
CustomPin pinData = new CustomPin();
pinData.Id = item.IdObjectInstance;
pinData.Label = item.ObjectClassName;
if (item.Points != null)
{
pinData.Position = new Position(item.Points.FirstOrDefault().Latitude, item.Points.FirstOrDefault().Longitude);
}
else
{
//add polygon
}
Guid id = Guid.NewGuid();
foreach (var s in item.Strings)
{
pinData.InfoBox.ItemsTest.Add(s.ClassParameterName+id + ": ", s.StringValue);
}
foreach (var i in item.Integers)
{
if (i.IntValue.HasValue)
{
pinData.InfoBox.ItemsTest.Add(i.ClassParameterName+id +": ", i.IntValue.Value.ToString());
}
}
foreach (var date in item.Dates.Take(1))
{
pinData.InfoBox.ItemsTest.Add(date.ClassParameterName + id + ": ", date.DateValue.ToString());
}
ListOfObjects.Add(pinData);
}
}
TemporalData.ObjectsData = ListOfObjects;
OnPropertyChanged(nameof(ListOfObjects));
OnPropertyChanged(nameof(TemporalData.ObjectsData));
});
}
Ive tried this xaml:
<ListView
ItemsSource="{Binding ListOfObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding InfoBox.ItemsTest}" Detail="{Binding Value}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
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
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;
}
}
}
I need to set images to get from a rest service inside every viewer of a ListView. These images change periodically to create a "gif" effect and, after a period, I recall the service to get the updated images about the webcam. The problem is that not all images are set but just a part of them and sometimes no one of them is set.
My code is the following:
public class WebcamListViewModel : BaseViewModel
{
public ICommand InitializeWebcamsCommand { set; get; }
public ICommand OpenVideoWebcamCommand { set; get; }
private List<Webcam> _ListOfWebcam { get; set; }
public List<Webcam> ListOfWebcam
{
get { return _ListOfWebcam; }
set
{
_ListOfWebcam = value;
OnPropertyChanged();
}
}
private IFolder folder;
private int _Counter { get; set; }
public int Counter
{
get { return _Counter; }
set
{
_Counter = value;
OnPropertyChanged();
}
}
private Task SetFrameOnViewTask;
private Task DownloadFramesTask;
CancellationTokenSource tokenSourceSetFrame = new CancellationTokenSource();
CancellationTokenSource tokenSourceDownloadFrames = new CancellationTokenSource();
CancellationToken cancellationTokenSetFrame;
CancellationToken cancellationTokenDownloadFrames;
public WebcamListViewModel(INavigationService navigationService, IApiAutostradeManagerFactory apiAutostradeManagerFactory) : base(navigationService,apiAutostradeManagerFactory)
{
OpenVideoWebcamCommand = new Command<Webcam>(async (webcam) => {
await navigationService.NavigateAsync(Locator.WebcamVideoPopUpPage);
Messenger.Default.Send(new InfoWebcamVideoMessage(webcam.c_mpr, webcam.c_uuid, webcam.t_str_vid));
});
InitializeWebcamsCommand = new Command(async () => await RunSafe(InitializeWebcams()));
InitializeWebcamsCommand.Execute(null);
cancellationTokenDownloadFrames = tokenSourceDownloadFrames.Token;
DownloadFramesTask = new Task(async () => {
cancellationTokenDownloadFrames.ThrowIfCancellationRequested();
while (true)
{
try
{
await DownloadAndSetWebcamImages();
await Task.Delay(2000);
if (cancellationTokenDownloadFrames.IsCancellationRequested)
{
// Clean up here, then...
cancellationTokenDownloadFrames.ThrowIfCancellationRequested();
}
}
catch (System.FormatException e)
{
Console.WriteLine(e.Message);
}
}
}, cancellationTokenDownloadFrames);
SetFrameOnViewTask = new Task(async () =>
{
cancellationTokenSetFrame.ThrowIfCancellationRequested();
while (true)
{
try
{
Counter++;
await Task.Delay(500);
if (cancellationTokenSetFrame.IsCancellationRequested)
{
Counter = 0;
// Clean up here, then...
cancellationTokenSetFrame.ThrowIfCancellationRequested();
}
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
}
}, cancellationTokenSetFrame);
}
private async Task InitializeWebcams()
{
folder = await FileSystem.Current.LocalStorage.GetFolderAsync("WebcamImages");
ListOfWebcam = await RepositoryHelper.Instance.WebcamRepository.GetItemsAsync();
ListOfWebcam = ListOfWebcam.OrderByDescending(x => x.n_prg_km).ToList();
try
{
if (DownloadFramesTask.Status == TaskStatus.Running)
{
try
{
tokenSourceDownloadFrames.Cancel();
}
finally
{
tokenSourceDownloadFrames.Dispose();
}
}
DownloadFramesTask.Start();
if (SetFrameOnViewTask.Status == TaskStatus.Running)
{
try
{
tokenSourceSetFrame.Cancel();
}
finally
{
tokenSourceSetFrame.Dispose();
}
}
SetFrameOnViewTask.Start();
}
catch (System.InvalidOperationException)
{}
}
private async Task DownloadAndSetWebcamImages()
{
await ImageService.Instance.InvalidateCacheAsync(CacheType.All);
foreach (var web in ListOfWebcam)
{
web.image1 = await GetWebcamFrame(web.frame1);
web.image2 = await GetWebcamFrame(web.frame2);
web.image3 = await GetWebcamFrame(web.frame3);
web.image4 = await GetWebcamFrame(web.frame4);
}
}
private async Task<ImageSource> GetWebcamFrame(string urlFrame)
{
try
{
var frameResponse = await ApiManager.GetWebcamFrame(urlFrame);
var base64Image = await frameResponse.Content.ReadAsStringAsync();
byte[] imageData = Convert.FromBase64String(base64Image);
return (ImageSource.FromStream(() => { return new MemoryStream(imageData); }));
}
catch (FormatException e)
{
throw e;
}
}
In my viewModel, I got two tasks: DownloadFramesTask and SetFrameOnViewTask, that every 500 ms increment a counter, that is used to show one of the four frames at the turn.
<ListView ItemsSource="{Binding ListOfWebcam}"
SeparatorVisibility="None"
CachingStrategy="RetainElement"
RowHeight="250"
VerticalOptions="FillAndExpand"
x:Name="ListWebcam">
<ListView.Header>
<StackLayout x:Name="HeaderStackLayout"
Padding="5,25,0,30"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand">
<Label x:Name="LabelHeader"
Text="Webcam:"
FontSize="Large"
FontAttributes="Bold"
TextColor="{x:Static statics:Palette.PrimaryColor}"
VerticalOptions="Center"
HorizontalOptions="Start" Margin="10,0,0,0"/>
</StackLayout>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<controls:ExtendedViewCell SelectedItemBackgroundColor="#fafafa">
<Grid x:Name="GridWebcam">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Frame Grid.Column="1"
Grid.RowSpan="2"
CornerRadius="20"
BackgroundColor="{x:Static statics:Palette.PrimaryColor}"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
HasShadow="True"
Margin="5,10">
<StackLayout>
<Label Text="{Binding t_str_vid,Converter={StaticResource WebcamNameConverter}}"
FontSize="Medium"
TextColor="White"
FontAttributes="Bold"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
</Label>
<Label TextColor="White"
FontSize="Medium"
Text="{Binding direzione,Converter={StaticResource DirectionToStringConverter}}"/>
<StackLayout Orientation="Horizontal">
<ffimageloading:CachedImage DownsampleToViewSize="True"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame1Converter}}"
IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame1Converter}}"
Source="{Binding image1}"/>
<ffimageloading:CachedImage x:Name="SecondFrame"
DownsampleToViewSize="True"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame2Converter}}"
IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame2Converter}}"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
Source="{Binding image2}"/>
<ffimageloading:CachedImage x:Name="ThirdFrame"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame3Converter}}"
IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame3Converter}}"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
Source="{Binding image3}"/>
<ffimageloading:CachedImage x:Name="FourthFrame"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame4Converter}}"
IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame4Converter}}"
VerticalOptions="FillAndExpand"
HorizontalOptions="StartAndExpand"
Source="{Binding image4}"/>
<iconize:IconButton Text="fas-play-circle"
FontSize="50"
HorizontalOptions="EndAndExpand"
VerticalOptions="EndAndExpand"
TextColor="White"
Command="{Binding BindingContext.OpenVideoWebcamCommand, Source={x:Reference ListWebcam}}"
CommandParameter="{Binding}"
BackgroundColor="Transparent"/>
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</controls:ExtendedViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In My dataTemplate I bind isVisible and isEnabled of every image with the counter, that is converted into a boolean thanks to the four converters.
I'll show just one of them:
public class VisibleFrame1Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((int)value % 4 == 0)
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This one, for example, is used to show the first frame when the condition about counter is satisfied.
My model class is the following:
public class Webcam : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int idWebcam { get; set; }
public string c_mpr { get; set; }
public int c_tel { get; set; }
public string c_uuid { get; set; }
public string direzione { get; set; }
public string frame1 { get; set; }
public string frame2 { get; set; }
public string frame3 { get; set; }
public string frame4 { get; set; }
public double n_crd_lat { get; set; }
public double n_crd_lon { get; set; }
public int n_ind_pri { get; set; }
public double n_prg_km { get; set; }
public int ramo { get; set; }
public int str { get; set; }
public string strada { get; set; }
public string t_str_vid { get; set; }
public string thumb { get; set; }
public ImageSource image1 { get; set; }
public ImageSource image2 { get; set; }
public ImageSource image3 { get; set; }
public ImageSource image4 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The expected result is to show every frame downloaded (I'm sure that every frame is downloaded, I checked one by one) in the view and download the updated version every tot time.
Maybe your problem is Webcam.cs , also need to use INotifyPropertyChanged for its property. As follow:
public class Webcam : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int idWebcam { get; set; }
public string c_mpr { get; set; }
public int c_tel { get; set; }
public string c_uuid { get; set; }
public string direzione { get; set; }
public string frame1 { get; set; }
public string frame2 { get; set; }
public string frame3 { get; set; }
public string frame4 { get; set; }
public double n_crd_lat { get; set; }
public double n_crd_lon { get; set; }
public int n_ind_pri { get; set; }
public double n_prg_km { get; set; }
public int ramo { get; set; }
public int str { get; set; }
public string strada { get; set; }
public string t_str_vid { get; set; }
public string thumb { get; set; }
// modified code
ImageSource image1 ;
public ImageSource Image1
{
set
{
if (image1 != value)
{
image1 = value;
OnPropertyChanged("Image1");
}
}
get
{
return image1 ;
}
}
ImageSource image2 ;
public ImageSource Image2
{
set
{
if (image2 != value)
{
image2 = value;
OnPropertyChanged("Image2");
}
}
get
{
return image2 ;
}
}
ImageSource image3 ;
public ImageSource Image3
{
set
{
if (image3 != value)
{
image3 = value;
OnPropertyChanged("Image3");
}
}
get
{
return image3 ;
}
}
ImageSource image4 ;
public ImageSource Image4
{
set
{
if (image4 != value)
{
image4 = value;
OnPropertyChanged("Image4");
}
}
get
{
return image4 ;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Any other you want updated property ,all need to use OnPropertyChanged in Model.Just in WebcamListViewModel.cs using , Webcam's property can not work.