Here is the code of XAML:
<Window x:Class="WpfApp1.MainWindow"
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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ComboBox x:Name="CB" SelectedValue="{Binding Model,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Value" VerticalAlignment="Center">
</ComboBox>
</Grid>
</Window>
And here is the code of code-behind:
public partial class MainWindow : Window
{
public List<TestModel> Models { get; set; } = new List<TestModel>();
TestModel _Model = new TestModel() { Key = "Joe", Value = "456" };
public TestModel Model
{
get => _Model; set
{
if (_Model != value)
{
_Model = value;
}
}
}
public MainWindow()
{
InitializeComponent();
Models.Add(new TestModel() { Key = "John", Value = "123" });
Models.Add(new TestModel() { Key = "Joe", Value = "456" });
Models.Add(new TestModel() { Key = "Kay", Value = "547" });
Models.Add(new TestModel() { Key = "Rose", Value = "258" });
CB.ItemsSource = Models;
this.DataContext = this;
}
public class TestModel
{
public string Key { get; set; }
public string Value { get; set; }
}
}
I bind the SelectedValue to the Model which is already existed in the List. but the selection is still blank.
What's wrong with my code? I need the combobox select the item correctly.
Model should return one of the items from Models.
Add this to the constructor before DataContext setter:
Model = Models[1];
The problematic line is
_Model = new TestModel() { Key = "Joe", Value = "456" } - you create an instance of TestModel that is not present in the list. Even though it has the same property values, it is not the same object.
ComboBoxes use the .Equals() method of the objects they are displaying to find the SelectedItem in the ItemsSource.
Since the Equals() method for clasess by default works by comparing object references, you would need to set the SelectedItem to an exact same instance as an object inside the ItemsSource.
Another approach you could take is overriding the Equals() method on your object and making it Equal by value comparison or use a record class which automatically makes your object comparable by value.
Related
I have an enum
enum SendDays
{
Maandag = 1,
Dinsdag,
Vandaag= 99
}
And a Class
public struct DayListModel
{
public int Id;
public string DayName;
}
I fill a list with days like this
private void Filldays()
{
foreach(int i in Enum.getValues(typeof(SendDays)))
{
DayListModel day =new DaylistModel()
{
Id = i,
Dayname = Enum.GetName(typeof(SendDays), i)
};
DayList.Add(day);
}
When I use this is in A telerik RadGridView in a radcombobox
like
<telerik:RadComboBox ItemsSource="{Bindning DayList}" DisplayMemberPath="DayName" SelectedValue="{Bindning DefaultSendDay}" SelectedValuePath="Id"/>
Whenever I change the selecteditem this is not passed.
Any suggestions?
Jeroen
Here is my advice, and how I got it to work:
Like #mm8 said, you should make the DayListModel a class not a struct, and turn the fields into properties. If you don't use properties, the DisplayMemberPath doesn't work. Some stuff doesn't work with structs (like using == to compare) so I would go with a class in this case.
Are your objects observable in your view model? I don't know telerik, but you should be calling some sort of PropertyChanged method so the UI can update. If you use a framework like MVVM light it has ObservableObjects and RaisePropertyChanged methods.
Here is a working example:
MainWindowVM.cs
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
namespace WpfApp1
{
public class MainWindowVM : ObservableObject
{
private List<DayListModel> _DayList;
public List<DayListModel> DayList
{
get { return _DayList; }
set
{
if (value != _DayList)
{
_DayList = value;
RaisePropertyChanged();
}
}
}
private DayListModel _DefaultSendDay;
public DayListModel DefaultSendDay
{
get { return _DefaultSendDay; }
set
{
if (value != _DefaultSendDay)
{
_DefaultSendDay = value;
RaisePropertyChanged();
}
}
}
public MainWindowVM()
{
DayList = new List<DayListModel>();
foreach (int i in Enum.GetValues(typeof(SendDays)))
{
DayListModel day = new DayListModel()
{
Id = i,
DayName = Enum.GetName(typeof(SendDays), i)
};
DayList.Add(day);
}
DayList = new List<DayListModel>(DayList);
}
}
public enum SendDays
{
Maandag = 1,
Dinsdag,
Vandaag = 99
}
public class DayListModel
{
public int Id { get; set; }
public string DayName { get; set; }
}
}
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowVM />
</Window.DataContext>
<Grid >
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox ItemsSource="{Binding DayList}" SelectedItem="{Binding DefaultSendDay}" DisplayMemberPath="DayName" >
</ComboBox>
<TextBlock Text="{Binding DefaultSendDay.DayName}"/>
</StackPanel>
</Grid>
</Window>
Screenshot:
Make sure that the types matches, i.e. if your DefaultSendDay source property is a byte, the Id of the DayListModel should also be a byte.
If it still doesn't work, try to convert the fields into properties and the struct to a class:
public class DayListModel
{
public byte Id { get; set; }
public string DayName { get; set; }
}
I have a simple wpf datagrid in a caliburn micro project. I am initializing the rows with instances of a TestClass. If the user selects one of this row headers I want to get the instance of TestClass. But all values and all the example on the Internet are only able to show the Text in the cell.
So How do I get the object I used to create the cell from the datagrid?
ShellViewModel.cs:
using System.Collections.Generic;
using System.Data;
using System.Windows.Controls;
using Caliburn.Micro;
namespace TestSelectionChanged.ViewModels
{
class ShellViewModel : Screen
{
private DataTable _profileColumnRows;
public DataTable ProfileColumnRows
{
get => _profileColumnRows;
set
{
if (Equals(value, _profileColumnRows)) return;
_profileColumnRows = value;
NotifyOfPropertyChange();
}
}
public ShellViewModel()
{
ProfileColumnRows = new DataTable("test");
ProfileColumnRows.Columns.Add("test1");
ProfileColumnRows.Columns.Add("test2");
// Here are the TestClass objects I want to get later
ProfileColumnRows.Rows.Add(new TestClass("testc","a","b"));
ProfileColumnRows.Rows.Add(new TestClass("testd", "c", "d"));
}
public void OnSelectionCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
var dataGrid = ((DataGrid) sender);
var column = dataGrid.SelectedCells[0].Column;
var columnIndex = dataGrid.SelectedCells[0].Column.DisplayIndex;
var rowIndex = dataGrid.Items.IndexOf(dataGrid.SelectedCells[0].Item);
}
}
class TestClass
{
public string name { get; set; }
public List<string> Items { get; set; }
public TestClass(string name, params string[] items)
{
this.name = name;
Items = new List<string>(items);
}
public override string ToString()
{
return name;
}
}
}
ShellView.xaml:
<UserControl x:Class="TestSelectionChanged.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<DataGrid
ItemsSource="{Binding ProfileColumnRows}"
cal:Message.Attach="[Event SelectedCellsChanged] = [Action OnSelectionCellsChanged($source,$eventArgs)]"/>
</StackPanel>
</UserControl>
I want to get the object in the first selected cell and if it is of the type TestClass I want to have access to its Items property. Is this even possible?
To begin with, it is unclear why you are attempting use a DataTable with TestClass instances as row. For this to work, you need to figure out how to convert each property of TestClass to different columns in DataTable. A better approach would be to bind the DataGrid to a List<TestClass> directly.
For example,
public ShellViewModel()
{
ProfileColumnRows.Add(new TestClass("testc", "a", "b"));
ProfileColumnRows.Add(new TestClass("testd", "c", "d"));
}
public List<TestClass> ProfileColumnRows {get;set;} = new List<TestClass>();
Now coming to your question of accessing the Selected Item, you could use the SelectedItem Property
<DataGrid
ItemsSource="{Binding ProfileColumnRows}" SelectedItem="{Binding SelectedItem}"
/>
And in ViewModel
public TestClass SelectedItem { get; set; }
I have grid with 10-15 columns. (I load data by datagrid.ItemsSource = myList.ToList()) Also I have textBox witch textChanged event. When I put here eg. "cat" I want to see only rows with value ...cat...
how do I make this?
LINQ queries are good for this sort of thing, the concept goes make a variable to store all of your rows (in the example called _animals) and then when the user presses a key in the text box use a query, and pass the result as the ItemsSource instead.
Here is a basic working example of how this would work, first the XAML for the Window.
<Window x:Class="FilterExampleWPF.MainWindow"
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:FilterExampleWPF"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="textBox1" Height="22" Margin="10,10,365,0" VerticalAlignment="Top" KeyUp="textBox1_KeyUp" />
<DataGrid x:Name="dataGrid1" Height="272" Margin="10,40,10,0" VerticalAlignment="Top" AutoGenerateColumns="True" />
</Grid>
</Window>
Next the code behind:
using System.Collections.Generic;
using System.Linq;
namespace FilterExampleWPF
{
public partial class MainWindow : System.Windows.Window
{
List<Animal> _animals;
public MainWindow()
{
InitializeComponent();
_animals = new List<Animal>();
_animals.Add(new Animal { Type = "cat", Name = "Snowy" });
_animals.Add(new Animal { Type = "cat", Name = "Toto" });
_animals.Add(new Animal { Type = "dog", Name = "Oscar" });
dataGrid1.ItemsSource = _animals;
}
private void textBox1_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
var filtered = _animals.Where(animal => animal.Type.StartsWith(textBox1.Text));
dataGrid1.ItemsSource = filtered;
}
}
public class Animal
{
public string Type { get; set; }
public string Name { get; set; }
}
}
For this example I created an Animal class, however you could substitute it for your own class that you need to filter. Also I enabled AutoGenerateColumns, however adding your own column bindings in WPF would still allow this to work.
Hope this helps!
This is my solution .
public class Animal
{
public string Type { get; set; }
public string Name { get; set; }
}
List<Animal> _animals = new List<Animal>();
public MainWindow()
{
InitializeComponent();
_animals.Add(new Animal { Type = "cat", Name = "Snowy" });
_animals.Add(new Animal { Type = "cat", Name = "Toto" });
_animals.Add(new Animal { Type = "dog", Name = "Oscar" });
dataGrid1.ItemsSource = _animals;
}
List<Animal> filterModeLisst = new List<Animal>();
private void searchBox_TextChanged(object sender, TextChangedEventArgs e)
{
filterModeLisst.Clear();
if (searchBox.Text.Equals(""))
{
filterModeLisst.AddRange(_animals);
} else
{
foreach (Animal anim in _animals)
{
if (anim.Name.Contains(searchBox.Text))
{
filterModeLisst.Add(anim);
}
}
}
dataGrid1.ItemsSource = filterModeLisst.ToList();
}
I have a ComboBox bound to a list of people in a simple ViewModel. The SelectedPerson is set in the constructor of the ViewModel, but when I run the application, the ComboBox is not set with its initial value. What am I doing wrong?
Notice that two instances of the MyPerson class should be considered equal when they have the same Id.
Unfortunately, I cannot modify the MyPerson to override Equals (it's third party).
The only option I've seen so far is to user the Adapter pattern to wrap the instances of this class and implement a custom Equals method there.
I feel that there should be a better method that is native to WPF to match items from a list by some kind of "key". In my case, the list of items and the selected item come from different sources, and that's why they have an Id property acting as a primary key.
I've played with SelectedValue and SelectedValuePath, but nothing works.
<Window x:Class="Test.MainWindow"
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:Test"
mc:Ignorable="d"
x:Name="Root"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ComboBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" SelectedValuePath="Id"
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedPerson}" />
</Window>
And this ViewModel as DataContext:
public class MainViewModel
{
public MainViewModel()
{
SelectedPerson = new MyPerson { Name = "Mary", Id = 1 };
}
public MyPerson SelectedPerson { get; set; }
public IEnumerable<MyPerson> People { get; } = new List<MyPerson>()
{
new MyPerson() {Name = "Mary", Id = 1 },
new MyPerson() {Name = "John", Id = 2 },
};
}
public class MyPerson
{
public string Name { get; set; }
public int Id { get; set; }
}
The problem is that the new object you create here
SelectedPerson = new MyPerson { Name = "Mary", Id = 1 };
isnt' the same as in your list so the Equals method would return False in all cases!
As someone else already suggested, you have to get the real object that is in the list by doing this:
public MainViewModel(){
SelectedPerson = People.First(x=> x.Name.Equals("Mary")); //Or: x.Id == 1
}
But there is also another solution: You can override the Equals function in your MyPerson class, so that every MyPerson instance with the same Name and/or Id would indeed be seen as the same person.
EDIT
As you're using ViewModels in your project, it would be better if you had a ViewModel for the MyPersonclass too. It would solve your problems and make your design better!
Change
public MainViewModel()
{
SelectedPerson = new MyPerson { Name = "Mary", Id = 1 };
}
To
public MainViewModel()
{
SelectedPerson = People.ElementAt(0);
}
Or if ou want by name:
public MainViewModel()
{
SelectedPerson = People.First(x=> x.Name=="Mary");
}
I have an Employee Class as shown below:
public class Employee : INotifyPropertyChanged
{
public Employee()
{
_subEmployee = new ObservableCollection<Employee>();
}
public string Name { get; set; }
public ObservableCollection<Employee> SubEmployee
{
get { return _subEmployee; }
set
{
_subEmployee = value;
NotifiyPropertyChanged("SubEmployee");
}
}
ObservableCollection<Employee> _subEmployee;
public event PropertyChangedEventHandler PropertyChanged;
void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
I am creating a collection of employee class in Main window constructor
and adding it to an observable collection of employee as shown below:
public partial class MainWindow : Window
{
public ObservableCollection<Employee> Emp { get; private set; }
public MainWindow()
{
InitializeComponent();
Emp = new ObservableCollection<Employee>();
Emp.Add(new Employee(){Name = "Anuj"});
Emp.Add(new Employee() { Name = "Deepak" });
Emp.Add(new Employee() { Name = "Aarti" });
Emp[0].SubEmployee.Add(new Employee(){Name = "Tonu"});
Emp[0].SubEmployee.Add(new Employee() { Name = "Monu" });
Emp[0].SubEmployee.Add(new Employee() { Name = "Sonu" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Harsh" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Rahul" });
Emp[2].SubEmployee.Add(new Employee() { Name = "Sachin" });
this.DataContext = this;
}
}
I have set the DataContext as self.
Now, in xaml file I have created a hierarchical template of treeview and binded data as shown below:
<Window x:Class="WpfApplication3.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>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubEmployee}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Now when I keep, TreeView ItemsSource="{Binding Emp}" , binding works properly
and I can see the tree view structure after running the code.
However when I keep TreeView ItemsSource="{Binding}", I see no result after running the code.
To my understanding, keeping ItemSource = "{Binding}" means I am binding to the evaluated value of the current datacontext.
As my datacontext is set to self, ItemSource = "{Binding}" should mean I am binding to the only property of DataContext i.e. Emp and I should get proper result.
Please help me in understanding the problem I am getting in keeping binding as
ItemSource = "{Binding}".
"To my understanding, keeping ItemSource = "{Binding}" means I am binding to the evaluated value of the current datacontext."
Correct AND that is the issue. ItemsSource expects the binding source to be of type IEnumerable but you are binding to Window.
"...should mean I am binding to the only property of DataContext i.e. Emp and I should get proper result."
No. No such "single property" assumption exists in WPFs binding conventions.
Change...
this.DataContext = this;
To...
this.DataContext = Emp;
Or, alternatively, change binding in XAML and specify the correct member on the DataContext to bind to using Path...
<TreeView ItemsSource="{Binding Path=Emp}">