Am I missing something or is there more to it that I am not getting? I'm working on a mobile app and have to use pickers for choices from a data table. To start, I have many such pickers that are key/value based. I have an internal ID and a corresponding Show value. The IDs do not always have 1, 2, 3 values such as originating from a lookup table and may have things as
KeyID / ShowValue
27 = Another Thing
55 = Many More
12 = Some Item
Retrieved as simple as
select * from LookupTable where Category = 'demo'
So I have this class below that is used for binding the picker via a list of records
public class CboIntKeyValue
{
public int KeyID { get; set; } = 0;
public string ShowValue { get; set; } = "";
}
Now, the data record that I am trying to bind to has only the ID column associated to the lookup. Without getting buried into XAML, but in general, I have my ViewModel. On that I have an instance of my data record that has the ID column.
public class MyViewModel : BindableObject
{
public MyViewModel()
{
// Sample to pre-load list of records from data server of KVP
PickerChoices = GetDataFromServerForDemo( "select * from LookupTable where Category = 'demo'" );
ShowThisRecord = new MyDataRec();
// for grins, I am setting the value that SHOULD be defaulted
// in picker. In this case, ID = 12 = "Some Item" from above
ShowThisRecord.MyID = 12;
}
// this is the record that has the "ID" column I am trying to bind to
public MyDataRec ShowThisRecord {get; set;}
// The picker is bound to this list of possible choices
public List<CboIntKeyValue> PickerChoices {get; set;}
}
I can’t bind to the index of the list, because that would give me 0, 1, 2, when I would be expecting the corresponding "ID" to be the basis of proper record within the list.
In WPF, I have in the past, been able to declare the show value for the screen, but also the bind value to the ID column in similar. So, the binding of the INT property on my "ShowThisRecord" would drive and properly refresh.
I can see the binding of SelectedItem, but that is the whole item of the KVP class which is not part of the MyDataRec. Only the ID is the common element between them.
What is the proper bindings to get this to work?
<Picker ItemDisplayBinding="{Binding ShowValue}"
SelectedItem="{Binding ???}" />
Just to confirm my record bindings are legitimate, my page has binding context to MyViewModel as I can properly see the ID via a sample text entry I added to the page via.
<Entry Text="{Binding Path=ShowThisRecord.MyID}"/>
I created a demo to test your code, and it works properly. The full demo is here. I also added a function to verify the selected item.
If you want to get the SelectedItem object synchronously, the MyViewModel should implement INotifyPropertyChanged, and I created a selectedRecordfield for SelectedItem, so you can do like this:
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
// Sample to pre-load list of records from data server of KVP
//PickerChoices = GetDataFromServerForDemo("select * from LookupTable where Category = 'demo'");
PickerChoices = new ObservableCollection<TestModel>() {
new TestModel{MyID = 5, ShowValue="test1"}, new TestModel{MyID = 9, ShowValue="test2"},
new TestModel{MyID = 18, ShowValue="test18"}, new TestModel{MyID = 34, ShowValue="test4"}
};
// Set the default selected item
// foreach (TestModel model in PickerChoices) {
// if (model.MyID == 18) { // Default value
// SelectedRecord = model;
// break;
// }
// }
ShowThisRecord = new TestModel();
// For grins, I am setting the value that SHOULD be defaulted
// in picker. In this case, ID = 12 = "Some Item" from above
ShowThisRecord.MyID = 12;
}
// This is the record that has the "ID" column I am trying to bind to
public TestModel ShowThisRecord { get; set; }
//*****************************************
TestModel selectedRecord; // Selected item object
public TestModel SelectedRecord
{
get { return selectedRecord; }
set
{
if (selectedRecord != value)
{
selectedRecord = value;
OnPropertyChanged();
}
}
}
//*****************************************
// The picker is bound to this list of possible choices
public ObservableCollection<TestModel> PickerChoices { get; set; }
}
class ViewModelBase
public class ViewModelBase: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the XAML content:
<Picker Title="Select a value" x:Name="mypicker"
ItemsSource="{Binding Path= PickerChoices}"
SelectedItem="{Binding SelectedRecord}"
ItemDisplayBinding="{Binding MyID}"/>
File xaml.cs:
public partial class MainPage : ContentPage
{
ObservableCollection<TestModel> items = new ObservableCollection<TestModel>();
MyViewModel testModel = null;
public MainPage()
{
InitializeComponent();
testModel = new MyViewModel();
BindingContext = testModel;
// This will also work
//if (testModel!=null && testModel.PickerChoices!=null) {
// for (int index=0; index< testModel.PickerChoices.Count; index++) {
// TestModel temp = testModel.PickerChoices[index];
// if (18 == temp.MyID) {
// mypicker.SelectedIndex = index;
// break;
// }
// }
//}
foreach (TestModel model in testModel.PickerChoices)
{
if (model.MyID == 18)
{ // Default value
testModel.SelectedRecord = model;
break;
}
}
}
// To show the selected item
private void Button_Clicked(object sender, EventArgs e)
{
if (testModel.SelectedRecord!=null) {
DisplayAlert("Alert", "selected Item MyID: " + testModel.SelectedRecord.MyID + "<--> ShowValue: " + testModel.SelectedRecord.ShowValue, "OK");
}
}
}
The result is:
You need to set the ItemsSource property to your list of CboIntValue items:
<Picker Title="Select a value"
ItemsSource="{Binding PickerChoices}"
ItemDisplayBinding="{Binding ShowValue}" />
After much work, I ended up writing my own separate class and template style for what I needed. Due to the length of it, and posting the source code for anyone to use, review, assess, whatever, I posted that out on The Code Project.
Again, the primary issue I had is if I have an integer key ID coming from a data source, the picker would not automatically refresh itself by just the given ID (int or string).
Related
I'm having hard times to show some simple labels in my Cartesian Bar Charts, I'm reading a lot around but nothing seems to work for me. I'm using the MVVM pattern in my project, so this is the code I have so far..
VIEW
<lvc:CartesianChart Grid.Row="2" Series="{Binding ChartDataSets}">
<lvc:CartesianChart.AxisX>
<lvc:Axis LabelsRotation="20" Labels="{Binding ColumnLabels}" Position="RightTop" >
<lvc:Axis.Separator >
<lvc:Separator Step="1"></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis LabelFormatter="{Binding Formatter}" Position="RightTop"></lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
DataModel
class DataModel : INotifyPropertyChanged
{
private double value;
public double Value
{
get => this.value;
set
{
this.value = value;
OnPropertyChanged();
}
}
private string label;
public string Label
{
get => this.label;
set
{
this.label = value;
OnPropertyChanged("Label");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel
class BackupStatsViewModel : INotifyPropertyChanged
{
ChartValues<DataModel> values = new ChartValues<DataModel>();
public SeriesCollection ChartDataSets { get; set; }
public ObservableCollection<string> ColumnLabels { get; set; }
public class ErrorPrt
{
public ErrorPrt(){
prtName = string.Empty;
Count = -1;
}
public string prtName { get; set; }
public int Count { get; set; }
}
public BackupStatsViewModel()
{
InitializeBarChartData();
}
private void InitializeBarChartData()
{
this.ColumnLabels = new ObservableCollection<string>(values.Select(dataModel => dataModel.Label));
var dataMapper = new CartesianMapper<DataModel>()
.Y(dataModel => dataModel.Value)
.Fill(dataModel => dataModel.Value > 15.0 ? Brushes.Red : Brushes.Green);
this.ChartDataSets = new SeriesCollection
{
new ColumnSeries
{
Values = values,
Configuration = dataMapper,
DataLabels = true
}
};
}
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
values.Add(new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count });
return arr;
}
}
But as you can see no labels are shown on the X axis.. really don't know how to bypass this problem in order to go on with my work..please can anyone show me the right way?
Your flow looks broken:
you first initialize the chart data from the constructor by calling InitializeBarChartData(), which also initializes the ColumnLabels collection. Then you create the underlying ErrorPtr items, which are the provider of the data for the column labels.
The result is that the ColumnLabels property is empty => no labels will be displayed.
Because you add the new ErrorPtr items to the values field and this field is of type ChartValues and this collection implements INotifyCollectionChanged, the chart will reflect those changes. You were lucky here.
But because you never update the ColumnLabels property after you have created the ErrorPtr items, the initially (after calling InitializeBarChartData from the constructor) empty ColumnLabels collection remains empty.
Solution 1
Fix the flow of your data model initialization and call InitializeBarChartData after PrtCount:
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
this.values.Add(new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count });
// Initialize the chat models.
// NOW the label data (the ErrorPrt.prtName) is generated
// and ready to be extracted from the ErrorPrt instances
InitializeBarChartData();
return arr;
}
Solution 2 (Recommended)
Since all involved collections implement INotifyCollectionChanged you can dynamically update every collection when new data arrives. You don't need to initialize the complete chart data like the SeriesCollection and the Mapper or the label formatter over and over again (like in Solution 1 - in case PrtCount will be called more than once).
You can continue to call InitializeBarChartData once from the constructor, like you are currently doing it.
Simply don't only update the values field, but also the ColumnLabels property:
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
{
var newDataModel = new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count };
// Here you update the column values
// and add the new items to the existing items of previous calls
this.values.Add(newDataModel);
// Also update the labels whenever new column data has arrived
this.ColumnLabels.Add(newDataModel.Label);
}
return arr;
}
So let me preface by saying that I am very new to WPF and MVVM.
I am using the mvvm design pattern for my application. My goal, is that I need to have two combo boxes loaded with content to select from( in this case, units to convert from and to). The content of these combo boxes is determined by a third combo box which determines the type of units to load.
So for example, the first combo box would let the user select a unit type, such as speed or temperature. So if I select temperature, the other two combo boxes would be loaded with a list of temperature units. Likewise if I select speed, then the list in the other two combo boxes would be replaced with units for speed.
I already have a class that handles the from and to conversion. But I'm a little lost with how to start working with these combo boxes. I have only done some basic things with combo boxes like loading content straight in the xaml. I have seen people make lists and somehow bind them but some it was a little overwhelming.
All I need is a good example and explanation to get me started. Would greatly appreciate it.
Everything you need is a ViewModel class to work with the binding.
Each combo box will binding the ItemSources to a Property in the ViewModel. Everytime the selected of the first combo box is change, you will update the data source of the second combo box.
Here is example of the ViewModel class:
namespace WpfApp1
{
class SampleVM : ViewModelBase
{
private ObservableCollection<UnitEntry> _comboBox1ItemSource;
private ObservableCollection<TypeEntry> _comboBoxTypeItemSource;
private int _selectedTypeIndex;
public ObservableCollection<UnitEntry> ComboBoxUnitItemSource
{
get => _comboBox1ItemSource;
set
{
_comboBox1ItemSource = value;
RaisePropertyChange(nameof(ComboBoxUnitItemSource));
}
}
public ObservableCollection<TypeEntry> ComboBoxTypeItemSource
{
get => _comboBoxTypeItemSource;
set
{
_comboBoxTypeItemSource = value;
RaisePropertyChange(nameof(ComboBoxTypeItemSource));
}
}
public int SelectedTypeIndex
{
get => _selectedTypeIndex;
set
{
_selectedTypeIndex = value;
RaisePropertyChange(nameof(SelectedTypeIndex));
//Here where we will handle the data in the second combo box depend on the Type value when it changed
if(value == 0)
{
ComboBoxUnitItemSource = GetDataUnitType1();
}
else
{
ComboBoxUnitItemSource = GetDataUnitType2();
}
}
}
public SampleVM()
{
InitData();
}
private void InitData()
{
//Init Type data
ComboBoxTypeItemSource = new ObservableCollection<TypeEntry>();
TypeEntry type1 = new TypeEntry(0, "Type 1");
TypeEntry type2 = new TypeEntry(1, "Type 2");
ComboBoxTypeItemSource.Add(type1);
ComboBoxTypeItemSource.Add(type2);
//Selected Index set to default by 0
SelectedTypeIndex = 0;
}
private ObservableCollection<UnitEntry> GetDataUnitType1()
{
//Get your real data instead of fake data below
ObservableCollection<UnitEntry> data = new ObservableCollection<UnitEntry>();
for (int i = 0; i < 5; i++)
{
UnitEntry unitEntry = new UnitEntry(i, $"Type 1 - Entry: {i}");
data.Add(unitEntry);
}
return data;
}
private ObservableCollection<UnitEntry> GetDataUnitType2()
{
//Get your real data instead of fake data below
ObservableCollection<UnitEntry> data = new ObservableCollection<UnitEntry>();
for (int i = 0; i < 5; i++)
{
UnitEntry unitEntry = new UnitEntry(i, $"Type 2 - Entry: {i}");
data.Add(unitEntry);
}
return data;
}
}
public class TypeEntry
{
public int ID { get; set; }
public string Name { get; set; }
public TypeEntry(int id, string name)
{
ID = id;
Name = name;
}
}
public class UnitEntry
{
public int ID { get; set; }
public string Name { get; set; }
public UnitEntry(int id, string name)
{
ID = id;
Name = name;
}
}
}
And here is the xaml class looks like:
<!-- The "Name" value is the Name property in the Entry class-->
<ComboBox Grid.Row="0"
Grid.Column="0"
Width="200"
Height="30"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedIndex="{Binding SelectedTypeIndex}"
ItemsSource="{Binding ComboBoxTypeItemSource}"/>
<ComboBox Grid.Row="0"
Grid.Column="1"
Width="200"
Height="30"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedIndex="0"
ItemsSource="{Binding ComboBoxUnitItemSource}"/>
Finally, important part, you need to assign the ViewModel to the View class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new SampleVM();
}
}
I have a DataGrid in my View as shown below.,
My Question is how can I Append the values from the textboxes to the row datagrid
I have make sure that the Model has All the properties, When I click on the Add button it overwrites the dataGrid and shows only one latest record the and my ViewModel look like this:
class BatchItemsViewModel : ViewModelBase
{
public SearchItemsModel msearchItems { get; set; }
ObservableCollection<SearchItemsModel> _BatchItemsGrid;
public ObservableCollection<SearchItemsModel> BatchItemsGrid
{
get { return _BatchItemsGrid; }
set
{
_BatchItemsGrid = value;
OnPropertyChanged("BatchItemsGrid");
}
}
private ICommand _addDataToBatchGrid;
public ICommand addDataToBatchGrid
{
get
{
return _addDataToBatchGrid;
}
set
{
_addDataToBatchGrid = value;
}
}
public BatchItemsViewModel()
{
msearchItems = new SearchItemsModel();
addDataToBatchGrid = new RelayCommand(new Action<object>(AddDataInBatchGrid));
}
public void AddDataInBatchGrid(object obj)
{
ObservableCollection<SearchItemsModel> batchGridData = new ObservableCollection<SearchItemsModel>();
var data = new SearchItemsModel
{
BatchNumber = msearchItems.BatchNumber,
MFDDate = msearchItems.MFDDate,
ExpiryDate = msearchItems.ExpiryDate,
Quantity = msearchItems.Quantity,
};
batchGridData.Add(data);
BatchItemsGrid = batchGridData; // HERE I am overwriting the datagrid
//How can I Append the batchGridData to BatchItemsGrid (BatchItemsGrid.Append(batchGridData)???)
}
}
NOTE: I have gone through the other threads as well in the community for the similar posts but I couldn't find the appropriate and please correct me if I am going in wrong direction.
public void AddDataInBatchGrid(object obj)
{
var data = new SearchItemsModel
{
BatchNumber = msearchItems.BatchNumber,
MFDDate = msearchItems.MFDDate,
ExpiryDate = msearchItems.ExpiryDate,
Quantity = msearchItems.Quantity,
};
this.BatchItemsGrid.Add(data);
}
...Should do the trick. (don't replace the whole collection, just add items to it and let the notification events handle the UI updates)
I need to show the WCF service Return Value(LIST) in Silverlight Listbox.
create GetAllAgents Class like,
public class GetAllAgents
{
public List<string> FirstName { get; set; }
public GetAllAgents(List<string> firstName)
{
FirstName = firstName;
}
}
The Following Method used for Consume the WCF Service
public partial class AgentQueue : UserControl
{
ChatACDService.ChatACDServiceClient ChatAcdClient = new ChatACDService.ChatACDServiceClient();
public ObservableCollection<GetAllAgents> _GetAllAgents = new ObservableCollection<GetAllAgents>();
public AgentQueue()
{
InitializeComponent();
LoadAgentList();
this.AllList.DataContext = _GetAllAgents;
}
private void LoadAgentList()
{
ChatAcdClient.GetAllAgentListCompleted += new EventHandler<GetAllAgentListCompletedEventArgs>(ChatAcdClient_GetAllAgentListCompleted);
ChatAcdClient.GetAllAgentListAsync();
}
void ChatAcdClient_GetAllAgentListCompleted(object sender, GetAllAgentListCompletedEventArgs e)
{
if (e.Error != null)
{
}
else
{
// AllAgents.ItemsSource = e.Result;
_GetAllAgents.Add(new GetAllAgents(e.Result.ToList()));
}
}
I use the following code For create List Box in XAML page
<ListBox x:Name="AllList" ItemsSource="{Binding}"
DisplayMemberPath="FirstName"
Margin="403,54,0,35" HorizontalAlignment="Left" Width="101" />
But The Output like ,
I need to show the WCF method's result(return type is list) in Listbox by using ObservableCollection.What are the changes are need to make in above Program?
Actually it works pretty well:
You ask to display the Member Path "FirstName" of your object GetAllAgents.
But the Member Path "FirstName" is a list of string.
So your XAML display what you expect from it: the toString() conversion of your memberPath.
And the default toString of your member path which is FirstName which is a list of string is: System.Collection.Generic.List[System.String]
I guess what you expect is that your list of first name should be the item source of your ListBox.
So if your only need is to display their firstName, just replace
public ObservableCollection<GetAllAgents> _GetAllAgents = new ObservableCollection<GetAllAgents>();
By
public ObservableCollection<string> _GetAllAgents = new ObservableCollection<string>();
and
_GetAllAgents.Add(new GetAllAgents(e.Result.ToList()));
By
foreach (var agentName in e.Result.ToList())
{
_GetAllAgents.Add(agentName);
}
And it will display the name of your agent.
If you need mor than that, you will need to create a viewModel per agent object and a dataTemplate to let know Silverlight how you want it to be display.
Hope it helps.
in my app there is gridview of my custom class. I am using custom data template and values are bound from SQLite. Now when user launch the app, the certain items (NOT SINGLE) should be pre-selected in gridview/listview. Gridview/listview allows multiple selection. How can I achieve this with SelectedItem property ?
UPDATE : I have followed this, it doesn't work for me. Returns 0 selections.
UPDATE 2 : I have posted the code
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
using (var db = new SQLite.SQLiteConnection(dbpath))
{
lvTags.ItemsSource = db.Table<Database.Tag>(); //lvTags is listview
if (MyList.Count > 0) //MyList is the static list of class "Database.Tag"
{
foreach (var item in MyList)
foreach (var lvitem in lvTags.Items)
if (lvitem.Equals(item))
lvTags.SelectedItems.Add(lvitem);
}
}
}
UPDATE 3:
public override bool Equals(object obj)
{
Tag tag = obj as Tag;
if (this.TagID == tag.TagID && this.TagName == tag.TagName)
return true;
else
return false;
}
Finally got answer from MSDN. Thanks ForInfo
XAML page
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView" SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ID}" Margin="0,0,5,0"/>
<TextBox Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
C#
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
LoadData();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
ObservableCollection<KiwiItem> sourceColl;
IList<KiwiItem> selectionList;
public void LoadData()
{
var dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "db.sqlite");
// Exec (1)
using (var db = new SQLite.SQLiteConnection(dbPath))
{
db.DropTable<KiwiItem>();
db.CreateTable<KiwiItem>();
db.RunInTransaction(() =>
{
db.Insert(new KiwiItem() { ID = 1, Title = "MyTitle1" });
db.Insert(new KiwiItem() { ID = 2, Title = "MyTitle2" });
db.Insert(new KiwiItem() { ID = 3, Title = "MyTitle3" });
db.Insert(new KiwiItem() { ID = 4, Title = "MyTitle4" });
});
this.sourceColl = new ObservableCollection<KiwiItem>();
this.selectionList = new List<KiwiItem>();
// Query the db. In practice, fill the sourceColl according to your business scenario
foreach (KiwiItem item in db.Table<KiwiItem>())
{
this.sourceColl.Add(item);
if (item.ID == 2 || item.ID == 4)
this.selectionList.Add(item);
}
}
// Exec (2)
this.listView.ItemsSource = this.sourceColl;
foreach (KiwiItem item in this.selectionList)
this.listView.SelectedItems.Add(item);
}
}
public class KiwiItem
{
[SQLite.AutoIncrement, SQLite.PrimaryKey]
public int ID { get; set; }
public string Title { get; set; }
}
You can use SelectedItems property.
//
// Summary:
// Gets the currently selected items.
//
// Returns:
// A collection of the currently selected items.
public IList<object> SelectedItems { get; }
You can use the SelectedItems property and call SelectedItems.Add() or SelectedItems.Remove() to add/remove items from selection.
If you use ItemsSource binding on the GridView you can use the ListViewExtensions.BindableSelection attached property from the WinRT XAML Toolkit (it should work with a GridView too since it is a subclass of ListViewBase) as in the sample page.
I deleted my original answer since yo are not using data binding and my answer wont be useful to you.
I found this just now that might be useful to you:
"SelectedItems property is read only, and cannot be set directly"
Hence, for a solution that may provide help, refer this article.