I have an object with an Order property I want to bind it in my datagrid. Is it possible ? I would like to change the order in a datagrid and use it in my object (With order property). Can I do it with binding ?
[Edit]
public class MyObject
{
...
public int Order {get; set;}
...
}
And I want to bind this property with Index of my row.
OK, still not really clear, but I'll try to provide some answer...
This is how I understand a question: you have a property of type Order', named 'Order' and want to edit this property in a data grid, for example using a Combo
Suppose you have an entity class like this (all code - just examples, because I don't know exact classes you have):
class Something : EntityBase {
................
public int Order { get; set; }
................
}
And it wrapped with data model like:
public SomethingModel : EntitydataModelBase<Something> {
................
public int Order {
get {
return this.Entity.Order;
}
set {
if (this.Entity.Order == value) return;
this.Entity.Order = value;
NotifyPropertyChanged("Order");
}
}
................
}
Also, there is a view model for a screen with your data grid, like this:
public SomethingListViewModel : ViewModelBase {
................
public IList<SomethingModel> _Items;
public IList<SomethingModel> Items {
get {
return _Items;
}
private set {
if (_Items == value) return;
_Items = value;
NotifyPropertyChanged("Items");
}
}
................
}
And finally your XAML:
................
<Grid x:Name="gridContainer"> <!-- We will use this name to make a reference in binding expression --!>
<DataGrid
ItemsSource="{Binding Items, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<!-- "Actually, don't remember a name of class for tempolated column" -->
<DataGridTemplatedColumn
Header="Order">
<DataGridTemplatedColumn.CellTemplate>
<DataTemplate>
<TextBox
Text="{Binding Order, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplatedColumn.CellTemplate>
</DataGridTemplatedColumn>
</DataGrid.Columns>
</DataGrid>
................
Hope this will help, if it won't, please provide your example code, and I'll try to add some example based on it
Related
after searching for hours and hours over weeks (at SO and google, I even asked ChatGPT), I was not able to find a Solution for my (simple?) Problem, although I have become close to my desired result.
What I have:
I have an c# wpf xaml application, where I display some datagrids (as CollectionViewSource), any of them are filled by List<MyModel>, where each Model Containing different strings as propertys.
I already use them as CollectionView in the XAML, have an filter in code behind, and can display the Lists.
XAML:
<Page.Resources>
<CollectionViewSource Filter="MyFilter" Source="{Binding Aktie.AktieBuchwerte}" x:Key="AktieBuchwerte" CollectionViewType="ListCollectionView">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Datum" Direction="Descending"/>
</CollectionViewSource.SortDescriptions>
</Page.Resources>
<ComboBox ItemsSource="{Binding Aktie.AktieBuchwerte}"
Margin="{StaticResource SmallLeftMargin}"
DisplayMemberPath="Quelle"
SelectedValuePath="Quelle"
x:Name="BuchWertComboBox"
SelectedValue="Marketscreener"/>
<DataGrid
AutoGenerateColumns="False"
GridLinesVisibility="All"
IsReadOnly="True"
CanUserAddRows="False"
ItemsSource="{Binding Source={StaticResource AktieBuchwerte}}"
KeyboardNavigation.TabNavigation="Once">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="DimGray" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Datum, StringFormat=yyyy}" Header="Jahr" />
<DataGridTextColumn Binding="{Binding Value, ConverterCulture=de-de}" Header="Buchwert" />
<DataGridTextColumn Binding="{Binding Schätzung, ConverterCulture=de-de}" Header="Schätzung" />
<DataGridTextColumn Binding="{Binding Quelle, ConverterCulture=de-de}" Header="Quelle" />
</DataGrid.Columns>
</DataGrid>
Code behind:
public partial class DataGridDetailPage : Page
{
public DataGridDetailPage(DataGridDetailViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
private void MyFilter(object sender, FilterEventArgs e)
{
if (e.Item is AktieBuchwert obj)
{
e.Accepted = obj.Quelle == (string)BuchWertComboBox.SelectedValue;
}
}
}
public class DataGridDetailViewModel : ObservableObject, INavigationAware
{
private readonly ISQLiteDataService _SQLiteDataService;
private Aktie _aktie;
public Aktie Aktie
{
get { return _aktie; }
set { SetProperty(ref _aktie, value); }
}
public DataGridDetailViewModel(ISQLiteDataService SQLiteDataService)
{
_SQLiteDataService = SQLiteDataService;
}
public async void OnNavigatedTo(object parameter)
{
if (parameter is int Id)
{
Aktie = await _SQLiteDataService.GetGridDetailDataAsync(Id);
}
}
}
public class Aktie
{
public List<AktieBuchwert> AktieBuchwerte { get; set; }
}
public class AktieBuchwert : AktieKennzahlen
{
public double Value { get; set; }
public bool Schätzung { get; set; }
public string Quelle { get; set; }
}
Actual result ComboBox & List
Actual result ComboBox dropdown & List
What I am not able to / where I could need some help after endless researching on my own:
I am not able to:
A: get rid of the multiple lines showing in the combobox dropdown. Basically there are two values in this case: string Value A (Boerse.de) or string Value B (Marketscreener), I want the ComboBox only to show the single values that are available in the PropertyField "Quelle" in the underlying List, not to show each property value of each Object (normal filter behaviour I would asume?)
B: get the List filtered dynamically. I only see items in the List, if I hardcode the "Marketscreener" or "Boerse.de" into the "SelectedValue" of the ComboBox. Good is, that I see the corresponding correct items (only with Marketscreener if selected e.g.), but as soon as I try so attach Binding to that Selected Value Field, my List is empty, e.g:
SelectedValue="{Binding Source={StaticResource AktieBuchwerte}, Path=Quelle}" is giving me following result:
Dynamic selected value:
I have tried many different combinations, and even that i have a filter now was hours and hours of research. I can not believe, that something so trivial like filtering a list is not possibile within some small amount of line of codes?
Would be glad if someone have the right answer to this...
Best regards
P.S.: For those of you, who are curious about ChatGPTs answer, here it is:
ChatGPTs answer on my Question
I usually use a additional list properties to do stuff like this.
This solution is not perfect but easy and effective.
Also it allows you to add more filter logic if required.
ViewModel (DataGridDetailViewModel):
public async void OnNavigatedTo(object parameter)
{
if (parameter is int id)
await Initialize(id);
}
private async Task Initialize(int id)
{
Aktie = await _SQLiteDataService.GetGridDetailDataAsync(id);
var quellen = Aktie.AktieBuchwerte.GroupBy(buchwert => buchwert.Quelle).Select(group => group.Key).ToList();
quellen.ForEach(quelle => Quellen.Add(quelle));
SelectedQuelle = quellen.FirstOrDefault();
}
public ObservableCollection<string> Quellen { get; set; } = new ObservableCollection<string>();
public List<Buchwert> FilteredBuchwerte => GetFilteredBuchwerte();
public string SelectedQuelle
{
get => _selectedQuelle;
set
{
if (Equals(value, _selectedQuelle))
return;
_selectedQuelle = value;
OnPropertyChanged(nameof(SelectedQuelle));
// make the UI fetch the filtered list again
OnPropertyChanged(nameof(FilteredBuchwerte));
}
}
public Aktie Aktie
{
get => _aktie;
set
{
if (Equals(value, _aktie))
return;
_aktie = value;
OnPropertyChanged(nameof(Aktie));
// make the UI fetch the filtered list again
OnPropertyChanged(nameof(FilteredBuchwerte));
}
}
private List<Buchwert> GetFilteredBuchwerte()
{
if (Aktie?.AktieBuchwerte == null)
return new List<Buchwert>();
IEnumerable<Buchwert> buchwerte = Aktie.AktieBuchwerte.ToList();
buchwerte = buchwerte.Where(buchwert => buchwert.Quelle == SelectedQuelle);
buchwerte = buchwerte.OrderByDescending(buchwert => buchwert.Datum);
return buchwerte.ToList();
}
The setter could also be something like:
set
{
SetProperty(ref _selectedQuelle , value);
// make the UI fetch the filtered list again
OnPropertyChanged(nameof(FilteredBuchwerte));
}
just make shure the UI is notified that the filtered list is changed as well.
View:
You won't need the <Page.Resources> block then, the MyFilter() in code behind will also no longer be required.
<ComboBox ItemsSource="{Binding Quellen}"
Margin="{StaticResource SmallLeftMargin}"
SelectedValue="{Binding SelectedQuelle, UpdateSourceTrigger=PropertyChanged}" />
<DataGrid AutoGenerateColumns="False"
GridLinesVisibility="All"
IsReadOnly="True"
CanUserAddRows="False"
KeyboardNavigation.TabNavigation="Once"
ItemsSource="{Binding FilteredBuchwerte}">
[...]
</DataGrid>
Edit:
Added null check to GetFilteredBuchwerte().
I have a list of objects as an ObservableCollection<MyObject>. I am already able to display the name property of these objects in a combobox using XAML within a DataGrid.
Now I have another object AnotherObject which has a property that is defined as a list of strings and each item of that list is the name property of MyObject mentioned above.
In the combobox I want to display the MyObject.name property preceeded by a checkbox.
Let's say that there are 30 items in the checkbox and an instance of AnotherObject.names holds three of them.
Now I want select the checkboxes of those items that are equal to the three items in AnotherObject.names.
How can I achieve this?
Some code:
MyObjectViewModel.cs:
public class MyObjectViewModel
{
private MyObject _myObject;
public MyObjectViewModel(MyObject myObject)
{
this._myObject = myObject;
}
public MyObject MyObject
{
get
{
return _myObject;
}
set
{
_myObject = value;
}
}
public string Name
{
get { return _myObject.Name; }
set
{
_myObject.Name = value;
}
}
public override string ToString()
{
return Name;
}
}
AnotherObjectRowViewmodel.cs:
public class AnotherObjectRowViewModel : INotifyPropertyChanged
{
private AnotherObject _anotherObject;
private ObservableCollection<MyObjectViewModel> _myObjects;
public AnotherObjectRowViewModel(AnotherObject anotherObject, ObservableCollection<MyObjectViewModel> myObjects)
{
this._anotherObject = anotherObject;
this._myObjects = myObjects;
}
public AnotherObject AnotherObject
{
get
{
return _anotherObject;
}
set
{
this._anotherObject = value;
}
}
public string Name
{
get { return _anotherObject.Name; }
set { _anotherObject.Name = value; }
}
public ObservableCollection<MyObjectViewModel> MyObjects {
get
{
return this._myObjects;
}
set
{
_myObjects = value;
}
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
//throw new NotImplementedException();
}
remove
{
//throw new NotImplementedException();
}
}
}
That's what I tried in the XAML file:
<DataGridTemplateColumn x:Name="NamesColumn" Header="Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="Name" DataContext="{Binding}" ItemsSource="{Binding Path=myObjects}" IsEditable="True" IsReadOnly="True"
VerticalAlignment="Center" SelectionChanged="OnDetailParamsSelectionChanged" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chbNames" Width="20" VerticalAlignment="Center" Checked="OnChbDetailParamsCheckBoxChecked" Unchecked="OnChbDetailParamsCheckBoxChecked"></CheckBox>
<TextBlock DataContext="{Binding Path=MyObject}" Text="{Binding Path=Name, Converter={StaticResource StringListConverter}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
An example:
The combobox holds a list of 30 names (Name1, ..., Name30);
AnotherObject.names is { Name1, Name2, Name4, Name7 };
In the combobox the selected items shall be Name1, Name2, Name4, Name7. All other items shall stay unselected.
Update 2019-01-06:
This means that the Combobox's ItemsSource={Binding} is MyObject but the checked items shall be stored in AnotherObject. This is why I get this exception whenever I tick a checkbox:
System.Windows.Data Error: 40 : BindingExpression path error: 'xxx' property not found on 'object' ''MyObjectViewModel' (HashCode=34649765)'. BindingExpression:Path=xxx.DetailParams; DataItem='MyObjectViewModel' (HashCode=34649765); target element is 'CheckBox' (Name='chbDetailParams'); target property is 'IsChecked' (type 'Nullable`1')
My XAML contains thefollowing code snippet according to the IsItemsSelectedConverter:
<UserControl.Resources>
<ctb:IsItemSelectedConverter x:Key="IsItemSelectedConverter"/>
</UserControl.Resources>
The checkboxes IsChecked property looks like this:
IsChecked="{Binding Path=Names, Mode=TwoWay, Converter={StaticResource IsItemSelectedConverter}}"
but it doesn't work. Debugging this code, the IsItemsSelectedConverter is never used.
Create a Boolean property in the view model of MyObject, i.e. in MyObjectViewModel that will return a value if Name is in the list of names in AnotherObject.
Create an IsItemSelectedConverter and pass in the list of objects from your data model. Implement IValueConverter in a new class and bind the IsChecked property for each checkbox to two things: value should be the current item, and parameter should be the list of items. The converter should determine whether the current item is in the list of items and return the appropriate boolean.
You will have to create an instance of the Converter for the UI to use. I usually define converters in a separate Resource Dictionary that I make globally available for all XAML files.
My IsItemSelectedConverter looks like this:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is MyObjectViewModel objectViewModel && parameter is AnotherObjectRowViewModel anotherObjectRowViewModel)
{
return anotherObjectRowViewModel.MyObjects.Contains(objectViewModel);
}
return false;
}
On the XAML, my CheckBox code looks like this: <CheckBox IsChecked="{Binding Mode=OneWay, Converter={StaticResource converter}, ConverterParameter={StaticResource viewModel} }" />
Please note: there are a few problems with the code that will prevent it from running as is, but I'm assuming these are artifacts of copying and pasting your actual code.
So I have a WPF app(with MVVM) and in this I have a combobox which binds to a table in my database and displays the values, this works just fine.
However, now I want to make a new combobox and bind it to the same table, but now I only want it to display SOME of the values. Is there a simple way to do this?
The table has has four entries but I only want to show 3 of them in this new combobox.
I know I could just make a new table in the database to bind to, but I might have to use several of these comboboxes(with different values) and I'd rather not go through all that bother if I can avoid it.
XAML:
<ComboBox
Name="cmComp"
MinWidth="150"
Margin="12 0 0 12"
ItemsSource="{Binding SelectedComponentLookup}"
DisplayMemberPath="ComponentChoice"
SelectedValuePath="ComponentChoice"
SelectedItem="{Binding ComponentChosen}">
</ComboBox>
VIEWMODEL:
private IEnumerable<ComponentLookupDto> _selectedComponentLookup;
public IEnumerable<ComponentLookupDto> SelectedComponentLookup
{
get { return _selectedComponentLookup; }
set
{
_selectedComponentLookup = value;
}
}
DTO:
public class ComponentLookupDto
{
public int ComponentLookupId { get; set; }
public string ComponentChoice { get; set; }
}
The way I achieve this is that I filter out the items I don't want to display in the getter for the property to which I bind my ItemsSource. :
XAML:
<ComboBox ItemsSource={Binding SelectedComponentLookupOther} ... />
And in your ViewModel:
public IEnumerable<ComponentLookupDto> SelectedComponentLookupOther
{
get { return _selectedComponentLookup.Where(c => c.SomeProperty == "however you want to pick it out"); }
}
I have a few Problems with databinding in WPF.
I have a ListBox which has a binding to a BindingList.
<ListBox x:Name="SampleListBox" ItemsSource="{Binding List1}" ItemContainerStyle="{StaticResource ListBoxStyle}" BorderThickness="0" SelectedIndex="0" Margin="0">
<ListBox.ItemTemplate>
<DataTemplate >
<Border x:Name="border" Width="185">
<TextBlock Text="{Binding name}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Till here, everything works fine. Now I have a DataGrid which should be linked to another BindingList and display some strings of it. So for example, if the first item of the ListBox is selected, the grid should show data of the first item of the second list.
I know, how it would work if both, the ListBox and the Grid get the data from the same list, but I have no idea, what to do, if this is not possible and there are two different lists.
You could bind SelectedIndex for the ListBox control to an property of type Int (Property1) in your ViewModel.
Also two-way bind SelectedItem in the DataGrid to another property (Property2) of the second list type.
In the setter for the Property1, change Property2 to be the item at the index of Property1 - i.e. List2[Property1]. It should change the selected item in the DataGrid.
So you want to use the listbox to, essentially, set a filter on the grid?
Note that LBItem and ViewModel below need to implement INotifyPropertyChanged and fire their PropertyChanged events when properties change, or none of this will work. But I'm leaving out the boilerplate for clarity.
Lots of ways to do that.
C#
public class LBItem {
public ViewModel Parent { get; set; }
public IEnumerable<String> SubItems {
get {
return Parent.List2.Where( /* filter items here */ );
}
}
}
public class ViewModel {
//
public ObservableCollection<LBItem> LBItems { get; set; }
public LBItem SelectedLBItem { get; set; }
public List<String> List2 { get; set; }
}
XAML
<ListBox
Name="MasterLB"
ItemsSource="{Binding LBItems}"
SelectedItem={Binding SelectedLBItem}"
/>
<DataGrid
ItemsSource="{Binding ElementName=MasterLB, Path=SelectedItem.SubItems}"
/>
That will work whether or not you bind MasterLB.SelectedItem to a property on the ViewModel. But as long as you are binding MasterLB.SelectedItem, you could just as easily bind DataGrid.ItemsSource to SelectedLBItem.SubItems on the ViewModel, like so:
<DataGrid
ItemsSource="{Binding Path=SelectedLBItem.SubItems}"
/>
But the ElementName binding is handy for a lot of things, so I'm giving you both.
You could also do something like this:
C#
public class LBItem {
public IEnumerable<String> Filter(IEnumerable<String> fullList) {
return fullList.Where( /* filter items here */ );
}
}
public class ViewModel {
public ObservableCollection<LBItem> LBItems { get; set; }
private LBItem _selectedItem;
public LBItem SelectedLBItem {
get { return _selectedItem; }
set {
_selectedItem = value;
List2Filtered = (null == _selectedItem)
? new List<String>()
: _selectedItem.Filter(List2).ToList();
}
}
public List<String> List2 { get; set; }
public List<String> List2Filtered { get; set; }
}
XAML
<ListBox
Name="MasterLB"
ItemsSource="{Binding LBItems}"
SelectedItem={Binding SelectedLBItem}"
/>
<DataGrid
ItemsSource="{Binding List2Filtered}"
/>
I have one model that implements INotifyPropertyChanged through BaseModel class.
It has other model as element inside of it.
class SIDPoslJavnaUstanova : BaseModel
{
private int? _sid_posl_javna_ustanova_id;
...
private decimal? _udaljenost;
private SIDJavnaUstanova _sid_javna_ustanova;
public SIDJavnaUstanova SidJavnaUstanova
{
get { return _sid_javna_ustanova; }
set {
if (_sid_javna_ustanova != value)
{
_sid_javna_ustanova = value;
if (_sid_javna_ustanova != null)
{
_sid_javna_ustanova_id = _sid_javna_ustanova.SidJavnaUstanovaId;
}
else
{
_sid_javna_ustanova_id = null;
}
RaisePropertyChanged("SidJavnaUstanova");
}
}
}
I have viewmodel that has observable collection of this model objects.
class BaseViewModel<T> : ObservableObject
{
private ObservableCollection<T> _elements = new ObservableCollection<T>();
public ObservableCollection<T> Elements
...
class SIDPoslJavnaUstanovaViewModel : BaseViewModel<SIDPoslJavnaUstanova>
{
}
}
And finally, mainviewmodel that is bound to view:
class MainViewModel : BaseViewModel<Store>
{
private SIDJavnaUstanovaViewModel _sidJavnaUstanovaViewModel;
private SIDJavnaUstanova _sidJavnaUstanova;
public SIDPoslJavnaUstanovaViewModel SidPoslJavnaUstanovaViewModel
{
get { return _sidPoslJavnaUstanovaViewModel; }
set
{
if (_sidPoslJavnaUstanovaViewModel != value)
{
_sidPoslJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidPoslJavnaUstanovaViewModel");
}
}
}
public SIDJavnaUstanovaViewModel SidJavnaUstanovaViewModel
{
get { return _sidJavnaUstanovaViewModel; }
set
{
if (_sidJavnaUstanovaViewModel != value)
{
_sidJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidJavnaUstanovaViewModel");
}
}
}
SidJavnaUstanova is only used to populate combobox, and to bind to object when choosen.
I have combobox in datagrid, that has mulitple lines. Element is SIDJAVNAUSTANOVA , and dropdown is SIDJAVNAUSTANOVAVIEWMODEL.
Dropdown is SIDJAVNAUSTANOVAVIEWMODEL.ELEMENTS
(cannot show you picture not enough reputation)
<src:BaseWindow.Resources>
<viewmod:MainViewModel x:Key="StoreViewM"/>
</src:BaseWindow.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="140" Header="{StaticResource name}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SidJavnaUstanova,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
DisplayMemberPath="Naziv"
SelectedValue="{Binding Path=SidJavnaUstanova, Mode=TwoWay}">
</ComboBox>
...
Everything is working fine except when combobox is changed, element SIDJavnaUstanova of object SIDPoslJavnaUstanova is changed, and I can catch this in its model property. But what I must have, is to catch change of this SidJavnaUstanova in viewmodel, so I can implement check-out if there are duplicates of sidjavnaustanova in sidposljavnaustanovaviewmodel.elements. I cannot realize how to do that.
Something like
SIDPoslJavnaUstanova.Elements.??? SIDJavnaUstanova
I cannot do this because elements is observable collection.
Maybe it is a bad model, please suggest something or help with current code.
You need to a) specify source for SelectedItem b) bind SelectedItem to the property of the same type, as elements in your collection (i.e. SIDPoslJavnaUstanova in your case).
This should work, i guess:
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SelectedModel,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Source={StaticResource StoreViewM}}"
DisplayMemberPath="Naziv">
</ComboBox>
.........................................
//MainViewModel
public SIDPoslJavnaUstanova SelectedModel
{
get { return _selectedModel; }
set
{
if (_selectedModel != value)
{
_selectedModel = value;
RaisePropertyChanged("SelectedModel");
}
}
}
And yes, this is some awful design.