WPF - Listview data binding to web socket data too slow - c#

I load about 190 items from a web socket and display them in a listview but the update on the data is too slow and not in synchrony with the real data. I tried with one item and it works perfectly. Is there a more efficient way to bind the data to the ObservableCollection? Should I try with virtualization? Or any other suggestions to make the code work as expected? Here is my code
The Models:
public class WSFuturesResponse
{
public string channel { get; set; }
public string market { get; set; }
public string type { get; set; }
public WSFuturesData data { get; set; } = new WSFuturesData();
}
public class WSFuturesData
{
public double? bid { get; set; }
public double? ask { get; set; }
public double? bidSize { get; set; }
public double? askSize { get; set; }
public double? last { get; set; }
public double? time { get; set; }
}
public class Tickers
{
public string Market { get; set; }
public double? Price { get; set; }
public Tickers(string market)
{
Market = market;
}
}
public class ApiFuturesData
{
public string name { get; set; }
}
public class ApiFuturesResponse
{
public bool success { get; set; }
public List<ApiFuturesData> result { get; set; }
}
The ViewModel
public class Ticker : INotifyPropertyChanged
{
protected string url = "wss://ftx.com/ws/";
protected WebSocket _webSocketClient;
public Action OnWebSocketConnect;
public event PropertyChangedEventHandler PropertyChanged;
private WSFuturesResponse _futuresResponse;
public List<Tickers> ListTickers = new List<Tickers>();
public WSFuturesResponse FuturesResponse
{
get
{
return _futuresResponse;
}
set
{
_futuresResponse = value;
OnPropertyChanged("FuturesResponse");
// Use the dispatcher to avoid System.NotSupportedException
App.Current.Dispatcher.Invoke(delegate
{
Tickers.Clear();
foreach (var future in ListTickers)
{
if (future.Market == FuturesResponse.market)
{
future.Price = FuturesResponse.data.last;
}
Tickers.Add(future);
}
});
}
}
public ObservableCollection<Tickers> Tickers { get; set; }
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Ticker()
{
Tickers = new ObservableCollection<Tickers>();
StartWebSocket();
}
public void StartWebSocket()
{
Client client = new Client("firstkey", "secondkey");
FtxRestApi api = new FtxRestApi(client);
StartConnection(this, client, api);
}
private async void StartConnection(Ticker wsApi, Client client, FtxRestApi api)
{
// get all futures data from the API
var futures = await api.GetAllFuturesAsync();
// parse the data
ApiFuturesResponse all_futures = JsonConvert.DeserializeObject<ApiFuturesResponse>(futures);
wsApi.OnWebSocketConnect += () =>
{
wsApi.SendCommand(FtxWebSocketRequestGenerator.GetAuthRequest(client));
foreach (ApiFuturesData future in all_futures.result)
{
if (future.name.Contains("PERP"))
{
// add the name to the list and use it to subscribe to the channel
ListTickers.Add(new Tickers(future.name));
wsApi.SendCommand(FtxWebSocketRequestGenerator.GetSubscribeRequest("ticker", future.name));
}
}
};
await wsApi.Connect();
}
public void WebsocketOnMessageReceive(object o, MessageReceivedEventArgs messageReceivedEventArgs)
{
FuturesResponse = JsonConvert.DeserializeObject<WSFuturesResponse>(messageReceivedEventArgs.Message);
}
And the XAML code:
<Window x:Class="FTXTradingClient.Views.TickerView"
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:vm="clr-namespace:FTXTradingClient.ViewModel"
xmlns:local="clr-namespace:FTXTradingClient.Views"
mc:Ignorable="d"
Title="FTX Trading Client" Height="850" Width="1200">
<Window.Resources>
<vm:Ticker x:Key="vm"/>
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="2"
Grid.Column="1"
ItemsSource="{Binding Tickers}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Market}"/>
<Label Grid.Column="1"/>
<TextBlock Grid.Column="2" Text="{Binding Price}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>

You could try to reset the Tickers property instead of first clearing all items in it and then add the new ones back:
public WSFuturesResponse FuturesResponse
{
get
{
return _futuresResponse;
}
set
{
_futuresResponse = value;
OnPropertyChanged("FuturesResponse");
foreach (var future in ListTickers)
{
if (future.Market == FuturesResponse.market)
{
future.Price = FuturesResponse.data.last;
}
}
// Use the dispatcher to avoid System.NotSupportedException
App.Current.Dispatcher.Invoke(delegate
{
Tickers = new ObservableCollection<Tickers>(ListTickers);
});
}
}
private ObservableCollection<Tickers> _tickers;
public ObservableCollection<Tickers> Tickers
{
get { return _tickers; }
set { _tickers = value; OnPropertyChanged("Tickers"); }
}

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

Storing Radiobutton selection from different groups to a list

I need to get all selected radio button tags and group name after user clicks on Appbar button submit and store it in a List.
So i can compare the user submitted answer list with the list from the server..
If i use Checked="Answer_Checked" , List is over written when i click another radio button in Question 2
public class RootObject
{
public RootObject(int id, string question, int qno, int qcount)
{
this.id = id;
this.question = question;
this.qcount = qcount;
this.qno = qno;
}
public int id { get; set; }
public string question { get; set; }
public int qcount { get; set; }
public int qno { get; set; }
public int time { get; set; }
}
public class AnswerObject
{
public AnswerObject(int question_id, int answer_id, string answer, int is_right_option)
{
this.question_id = question_id;
this.answer_id = answer_id;
this.answer = answer;
this.is_right_option = is_right_option;
}
public int question_id { get; set; }
public int answer_id { get; set; }
public string answer { get; set; }
public int is_right_option { get; set; }
}
public class Question
{
public string QuestionName { get; set; }
public int qcount { get; set; }
public int qno { get; set; }
public ObservableCollection<Option> options { get; set; }
}
public class Option
{
public string QuestionAnswer { get; set; }
public string groupname { get; set; }
public int IsCorrect { get; set; }
}
C# Coding
var result1 = await response1.Content.ReadAsStringAsync();
var objResponse1 = JsonConvert.DeserializeObject<List<RootObject>>(result1);
var result2 = await response2.Content.ReadAsStringAsync();
var objResponse2 = JsonConvert.DeserializeObject<List<AnswerObject>>(result2);
for (int i = 0; i < objResponse1.LongCount(); i++)
{
ObservableCollection<Option> options1 = new ObservableCollection<Option>();
for (int j = 0; j < objResponse2.LongCount(); j++)
{
if (objResponse1[i].id == objResponse2[j].question_id)
{
options1.Add(new Option() { QuestionAnswer = objResponse2[j].answer, IsCorrect = objResponse2[j].is_right_option, groupname = objResponse2[j].question_id.ToString() });
}
}
questions.Add(new Question() { QuestionName = objResponse1[i].question, qno=i + 1, qcount =objResponse1.Count, options = options1 });
}
flipView.ItemsSource = questions;
XAML Coding
<FlipView x:Name="flipView" HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding}" Margin="0,35,0,0">
<FlipView.ItemTemplate>
<DataTemplate>
<ListView Name="ItemData" SelectionMode="None" ItemsSource="{Binding}" >
<Grid x:Name="ContentPanel">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="Testing" Margin="10,20" FontSize="22.333">
<Run Text="{Binding qno}"/>
<Run Text="of"/>
<Run Text="{Binding qcount}"/>
</TextBlock>
<TextBlock x:Name="Question" Text="{Binding QuestionName}" Margin="10,60" VerticalAlignment="Top" HorizontalAlignment="Left" FontSize="22.333" TextWrapping="Wrap"/>
<ListBox Grid.Row="1" Padding="0" Margin="10,-10" ItemsSource="{Binding options}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton x:Name="Answer" GroupName="{Binding groupname}" Checked="Answer_Checked" Tag="{Binding IsCorrect}" Margin="10,2">
<RadioButton.Content>
<TextBlock Text="{Binding QuestionAnswer}" Foreground="White"/>
</RadioButton.Content>
</RadioButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ListView>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Answer in JSON Format
[{"question_id":3,"answer_id":1,"answer":"10%","is_right_option":0},{"question_id":3,"answer_id":2,"answer":"10.25%","is_right_option":1},{"question_id":3,"answer_id":3,"answer":"10.5%","is_right_option":0},{"question_id":3,"answer_id":4,"answer":"None of these","is_right_option":0},{"question_id":4,"answer_id":5,"answer":"Rs. 2.04","is_right_option":1},{"question_id":4,"answer_id":6,"answer":"Rs. 3.06","is_right_option":0},{"question_id":4,"answer_id":7,"answer":"Rs. 4.80","is_right_option":0},{"question_id":4,"answer_id":8,"answer":"Rs. 8.30","is_right_option":0}]
Question in JSON Format
[{"id":3,"question":"An automobile financier claims to be lending money at simple interest, but he includes the interest every six months for calculating the principal. If he is charging an interest of 10%, the effective rate of interest becomes: ","time":1},{"id":4,"question":"What is the difference between the compound interests on Rs. 5000 for 1 years at 4% per annum compounded yearly and half-yearly? ","time":1}]
I solved it using Dictionary instead of using List
private void Answer_Checked(object sender, RoutedEventArgs e) // Radio button click
{
var radio = sender as RadioButton;
bool check = Convert.ToBoolean(radio.IsChecked);
if(check)
{
Answer[Convert.ToInt16(radio.GroupName)] = Convert.ToInt16(radio.Tag);
}
}
public async void Check_Result() // Evaluate result
{
foreach (KeyValuePair<int, int> count in Answer)
{
if (count.Value == 1)
{
result++;
}
}
MessageDialog showresult = new MessageDialog(result.ToString());
await showresult.ShowAsync();
Frame.Navigate(typeof(MainPage), null);
}
public void TestSubmit_Click(object sender, RoutedEventArgs e) // AppBar button click
{
Check_Result();
}

WPF DataGrids for hierarchical information

I have an application that contains an ObservableCollection<Foo>, and Foo in turn contains an ObservableCollection<Bar>. I would like a pair of datagrids, one showing the collection of Foo objects in the application and the other showing the collection of Bar objects in the Foo that's currently selected in the first datagrid (and I want to be able to add, update and delete entries in both datagrids).
So far I've got the following XAML:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding Foos}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Foo Name" Binding="{Binding Name}" Width="Auto" IsReadOnly="False" />
</DataGrid.Columns>
</DataGrid>
<GridSplitter HorizontalAlignment="Right" VerticalAlignment="Stretch" Grid.Column="1" ResizeBehavior="PreviousAndNext" Width="5" Background="Gray" />
<DataGrid Grid.Column="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Bar Name" Width="Auto" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
And the following code:
using System;
using System.Windows;
using System.Collections.ObjectModel;
namespace Test
{
public class Foo
{
static int _nextId;
public int Id { get; private set; }
public String Name { get; set; }
public ObservableCollection<Bar> Bars { get; set; }
public Foo()
{
Id = _nextId++;
Name = String.Empty;
Bars = new ObservableCollection<Bar>();
}
}
public class Bar
{
static int _nextId;
public int Id { get; private set; }
public String Name { get; set; }
public Bar()
{
Id = _nextId++;
Name = String.Empty;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Foo> Foos { get; set; }
public MainWindow()
{
Foos = new ObservableCollection<Foo>();
Foo newFoo;
for (int index = 0; index < 5; ++index)
{
newFoo = new Foo();
newFoo.Name = String.Format("Foo {0}", index);
Foos.Add(newFoo);
}
InitializeComponent();
DataContext = this;
}
}
}
Obviously I've not bound the 2nd DataGrid yet, because I've not got the faintest idea how to do it! All the examples I can find assume I'm binding DataTables, not custom objects, and bind to a relation on the DataTables. I don't really understand binding all that well yet. Can anybody tell me how to bind the 2nd table?
(And yes, if you've seen my other recent questions, I am giving WPF another shot after not getting on well with it in the early days).
Thanks in advance.
Hi If you want editable grids first of all you will have to implement INotifyPropertyChanged like
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
Foos = new ObservableCollection<Foo>();
}
public ObservableCollection<Foo> Foos { get; set; }
}
public class Foo : INotifyPropertyChanged
{
static int _nextId;
public int Id { get; private set; }
public ObservableCollection<Bar> Bars { get; set; }
public Foo()
{
Id = _nextId++;
Name = String.Empty;
Bars = new ObservableCollection<Bar>();
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
Notify("Name");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Bar : INotifyPropertyChanged
{
static int _nextId;
public int Id { get; private set; }
public Bar()
{
Id = _nextId++;
Name = String.Empty;
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
Notify("Name");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In xaml Binding for first Grid is corrrect and for second grid you can set ItemsSource as the the selectedItem of First grid using ElementName
<DataGrid Grid.Column="2" ItemsSource="{Binding ElementName=gridTop, Path=SelectedItem.Bars}">
<DataGrid.Columns>
<DataGridTextColumn Header="Bar Name" Binding="{Binding Name}" Width="Auto" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
Use Binding to Element
Name the top grid gridTop
DataContext="{Binding ElementName=gridTop, Path=SelectedItem.Bars}"

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