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();
}
}
Related
Here is an exemplary code piece:
Model.cs
public class Datas
{
public int Region;
public int Frequency;
public int Amplitude;
}
MainViewModel.cs
public Datas Data1;
public MainViewModel()
{
DataList = new ObservableCollection<Datas>();
GetDatas();
}
public ObservableCollection<Datas> DataList { get; set; }
public void GetDatas()
{
...
...
var command = new SqlCommand($"Select [Region], [Frequency], [Amplitude].. WHERE REGION = '{SelectedRegion}..");
var dataReader = command.ExecuteReader();
while (dataReader.Read())
{
var Data1 = new Datas();
Data1.Region = dataReader["Region"];
Data1.Frequency = dataReader["Frequency"];
Data1.Amplitude = dataReader["Amplitude"];
DataList.Add(Data1);
}
connection.close();
}
(I tried to use DevExpressMVVM tools.)
MainView.xaml
<dxg:GridControl ItemsSource = "{Binding DataList} ...../>
Now; I can see the table on the window with Region, Frequency, and Amplitude columns and their values.
But I want only to show the 2 columns; maybe like Frequency and Amplitude.
What would be the most efficient way to do this?
In XAML or in code you can set the column visibility to hidden.
If you give a name to your datagrid you can access his properties.
<dxg:GridControl Name="myDatagrid" ItemsSource = "{Binding DataList} ...../>
then in code :
myDataGrid.Columns[0].Visibility = Visibility.Hidden;
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).
I´m quite new to WPF and I want to show in my UserControl something like this:
Sample Image
I get data from different sources:
Columns:
ID: always present
Problem 1 ...n: I have an XML-file like this one
from which I want to get the Columns for the Problems. There can be a variable number of Problems.
Grade: always present
Rows:
Each Row should only contain Textboxes (some are read-only for the user).
The Data in the ID Column comes from a file.
The Values for the Problems are input by the user.
The Data under Grade will be calculated.
The input values for the Problems and the calculated value for Grade are related to the ID.
I tried to implement this by using a DataGrid and setting up DataGridTemplateColumn for the Problem Columns but was not really successful. I think this would be the right control in this case, but I can´t really figure out how to set this up.
I thought about building this "Table" manually, by adding Labels and Textboxes for the "Problems" depending on the Data. But then my ViewModel should know something about the View which violates MVVM.
Thanks and best regards.
EDIT:
Thanks, #Ivan Furdek for your help so far!
I got to show now each textbox and column for the Problems and can edit them.
But my Collection won´t be updated -.- so my List<double> PointsPerProblems stays on the intialized values
Here is my XAML:
<ItemsControl ItemsSource="{Binding PointsPerProblems, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="70">
<TextBox Text="{Binding Path=., Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
TextAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My Model:
public class GradingModel
{
public string MatriculationNumber { get; set; }
public List<double> PointsPerProblems { get; set; }
public double Grade { get; set; }
public double TotalScore { get; set; }
}
And the specific parts of my ViewModel:
public ObservableCollection<GradingModel> Gradings
{
get
{
return this.gradings;
}
set
{
this.gradings = value;
this.OnPropertyChanged(nameof(this.Gradings));
}
}
public List<string> ProblemList
{
get
{
return this.problemList;
}
set
{
this.problemList = value;
this.OnPropertyChanged(nameof(this.ProblemList));
}
}
private void GetGradingForm()
{
ExamRatingDTO examRatingModel = new ExamRatingDTO();
List<StudentDTO> students = new List<StudentDTO>();
this.messageBoxService.ShowInfoMessage(
"Please Select a xml-File containing the Exam Ratings.",
"Select a Exam Rating File.");
try
{
examRatingModel = this.fileDialogService.OpenLoadFileDialog<ExamRatingDTO>();
}
catch (InvalidOperationException ex)
{
this.messageBoxService.ShowErrorMessage("Please select a correct Exam Rating File.", ex);
}
this.messageBoxService.ShowInfoMessage(
"Please Select a xml-File containing the Students Information.",
"Select a Student Information File.");
try
{
students = this.fileDialogService.OpenLoadFileDialog<List<StudentDTO>>();
}
catch (InvalidOperationException ex)
{
this.messageBoxService.ShowErrorMessage("Please select a correct Students File.", ex);
}
foreach (var student in students)
{
this.Gradings.Add(new GradingModel()
{
MatriculationNumber = student.MatriculationNumber.ToString(),
PointsPerProblems = new List<double>(),
Grade = 0.0,
TotalScore = 0.0
});
}
List<string> tmpProblemList = new List<string>();
foreach (var problem in examRatingModel.PointsPerProblems)
{
tmpProblemList.Add(problem.ProblemName);
}
foreach (var grading in this.Gradings)
{
for (int i = 0; i < tmpProblemList.Count; i++)
{
grading.PointsPerProblems.Add(0.0);
}
}
this.ProblemList = tmpProblemList;
}
EDIT
Ok found the Solution for the last problem here in 2nd edit of the answer
WPF: How to make DataGrid binding with dynamic columns editable?
You'll need to parse the xml into a list of objects like
public class Student
{
public int Id { get; set; }
public List<decimal> ProblemScores { get; set; }
public DecimalGrade
{
get
{
return ProblemScores.Average();
}
}
After that I suggest you follow this method to get the required display: https://blogs.msmvps.com/deborahk/populating-a-datagrid-with-dynamic-columns-in-a-silverlight-application-using-mvvm/
Make sure to use two way binding, and UpdateSourceTrigger = PropertyChanged so the changes get propagated back to the list.
The columns for id and score should be have their IsReadOnly property set to true.
I have a ComboBox defined like this:
<ComboBox
ItemsSource="{Binding Choices}"
SelectedItem="{Binding Value}"
Text="{Binding Text}"
IsEditable="True"
TextSearch.TextPath="Label"
DisplayMemberPath="Label" />
Here is my view Model:
public class ComboBoxViewModel : ViewModelBase
{
private string _selectedCode;
public ReadOnlyObservableCollection<ComboBoxItem> Choices { get; }
public ComboBoxItem Value
{
get { return this.Choices.FirstOrDefault(choice => choice.Code == _selectedCode); }
set
{
this.SetCode(value?.Code)
}
}
public string Text
{
get { return this.Value?.Label ?? _selectedCode; }
set
{
// Only set the code if no pre-defined code can be selected
if (this.Value == null)
{
this.SetCode(value)
}
}
}
public ComboBoxViewModel()
{
this.Choices = [..];
}
public bool SetCode(string code)
{
if (_selectedCode != code)
{
_selectedCode = code;
// Tried all the combination with/without/different order with no change
this.RaisePropertyChanged(nameof(this.Value));
this.RaisePropertyChanged(nameof(this.Text));
}
}
}
public class ComboBoxItem
{
public string Code { get; }
public string Label { get; }
public ComboBoxItem(string code, string label)
{
this.Code = code;
this.Label = label;
}
}
The Choices collection is initialized with some pair: Code,Label. I want to display the Label to the user and use the Code in my business layer. I also want my user to input its own code in the ComboBox (this is why the IsEditable dependency property is set to True and why I also bind Text on my ViewModel).
Everythings works fine when directly bind my ViewModel on the Control. The _selectedCode is updated prioritary with the selected Choices element or with the manual input if necessary.
My problem occurs when I pre-set the _selectedCode using the SetCode method. The Value property is no longer updated when I chose a new existing Choice in the ComboBox...
Is it possible to bind both SelectedItem and Text of a ComboBox? Do you have an idea why the bound properties are not updated after a programmatic initialization? It is like the event is not fired anymore...
I am working with DataGrids but I am struggling to binding my data since the number of columns varies depending of the info that has to be showed.
So, what I have tried to do is to create and object which contains all the columns and rows that I need at some point and binding this object to the ItemsSource property. Since I have worked with DataGridViews in WindowsForms I have in mind something like this:
DataTable myTable = new DataTable();
DataColumn col01 = new DataColumn("col 01");
myTable.Columns.Add(col01);
DataColumn col02 = new DataColumn("col 02");
myTable.Columns.Add(col02);
DataRow row = myTable.NewRow();
row[0] = "data01";
row[1] = "data02";
myTable.Rows.Add(row);
row = myTable.NewRow();
row[0] = "data01";
row[1] = "data02";
myTable.Rows.Add(row);
But I haven't been able to find a way to do the same thing in WPF since I need some columns to be DataGridComboBoxColumns for example.
Actually I have read many post about it in this site, but none of them helped to me. I am really lost.
Could anyone help me? I just need to be able to create a table which may contain DataGridTextColumns or `DataGridComboBoxColumns, etc, In order to bind this final object to the DataGrid's ItemsSource property.
Hope someone can help me.
Okay, let me try to take an example which is similar to your needs
Let's assume we use this class:
public class MyObject
{
public int MyID;
public string MyString;
public ICommand MyCommand;
}
And we are willing to display a DataGrid listing the ID, and having as a second column a Button, with the property MyString as content, which, when clicked, launches the ICommand MyCommand which opens in a new window whatever you want.
Here is what you should have on the View side:
<DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding MyID}" />
<DataGridTemplateColumn Header="Buttons">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding MyString}" Command="{Binding MyCommand}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This will show a DataGrid taking all the content in an IEnumerable<MyObject> named 'MyList', and shows two columns as defined before.
Now if you need to define the command.
First, I recommend you read this introductory link to MVVM and take the RelayCommand class (that's what we're gonna use for your problem)
So, in your ViewModel, the one which defines the MyList, here is how you should define some of the useful objects:
public ObservableCollection<MyObject> MyList { get; set; }
// blah blah blah
public void InitializeMyList()
{
MyList = new ObservableCollection<MyObject>();
for (int i = 0; i < 5; i++)
{
MyList.Add(InitializeMyObject(i));
}
}
public MyObject InitializeMyObject(int i)
{
MyObject theObject = new MyObject();
theObject.MyID = i;
theObject.MyString = "The object " + i;
theObject.MyCommand = new RelayCommand(param =< this.ShowWindow(i));
return theObject
}
private void ShowWindow(int i)
{
// Just as an exammple, here I just show a MessageBox
MessageBox.Show("You clicked on object " + i + "!!!");
}
A simple example of binding to a ObservableCollection of a custom object. Add more properties to the custom object to match what you want your rows to look like.
using System.Collections.ObjectModel;
public MyClass
{
public ObservableCollection<MyObject> myList { get; set; }
public MyClass()
{
this.DataContext = this;
myList = new ObservableCollection<MyObject>();
myList.Add(new MyObject() { MyProperty = "foo", MyBool = false };
myList.Add(new MyObject() { MyProperty = "bar", MyBool = true };
}
}
public MyObject
{
public string MyProperty { get; set; }
// I believe will result in checkbox in the grid
public bool MyBool { get; set; }
//...as many properties as you want
}
with xaml
<DataGrid ItemsSource= "{Binding myList}" />
Might be some small syntax errors, I wrote that entirely within the SO window.
I am new to WPF and used Damascus's example to learn binding of a List to a datagrid. But when I used his answer I found that my datagrid would populate with the correct number of rows but not with any of the properties from the MyObject class. I did a bit more searching then stumbled across what I had to do by accident.
I had to encapsulate the MyObject class properties to have them show. It wasn't enough to have them be public.
Before:
public class MyObject
{
public int MyID;
public string MyString;
public ICommand MyCommand;
}
After:
public class MyObject
{
private int _myID;
public int MyID
{
get { return _myID; }
set { _myID = value; }
}
private string _myString;
public string MyString
{
get { return _myString; }
set { _myString = value; }
}
private ICommand _myCommand;
public ICommand MyCommand
{
get { return _myCommand; }
set { _myCommand = value; }
}
}
Thank you Damascus for a great example and thank you Dante for a great question. I don't know if this is due to a change in version since your post but hopefully this will help others new to WPF like me.