Retrieve column name from a gridcontrol in mvvm design - c#

Hi I am working on a WPF project in which I have a grid control . The itemsSource property of the grid control is bound to a datatable in my viewmodel. I am following the mvvm pattern, so my question is that I need to bind the selectedcell property of the grid control to a property in my view model class. Is it possible to determine the name of the column in which the cell resides by binding it to a property in the view model class. I know an event handler can be attached to the cell which would call a function in the code behind the view, but I dont wish to follow that approach since it would not be mvvm. Kindly help me with any suggestions.

In your XAML bind the CurrentCell property to a DataGridCellInfo in your View Model:
<DataGrid SelectionUnit="Cell"
SelectionMode="Single"
ItemsSource="{Binding MyDataTable}"
CurrentCell="{Binding SelectedCellInfo, Mode=OneWayToSource}"/>
Then in your View Model you can access the header from the bound object:
public DataGridCellInfo SelectedCellInfo
{
get { return _selectedCellInfo; }
set
{
_selectedCellInfo = value;
OnPropertyChanged("SelectedCellInfo");
_columnName = _selectedCellInfo.Column.Header;
}
}

Related

How to Bind entire datagrid to view MVVM following the MVVM pattern

I want to dynamically display data in form a of a table. The control I chose for different reasons is a DataGrid. That solution worked to a certain point, but when I needed to dyanmically alter which properties I wanted to display in the datagrid I encountered problems. When I bind its ItemsSource directly to an observable it shows all properties in the specific object, which I dont want. I want to Create a datagrid programatically and push it into the view somehow. Is that even possible?
I've been looking now for a long time but I can't seem to find a good solution. It's also important that the solution follows the MVVM-pattern.
public class MyViewModel
{
public Datagrid dataGrid = null;
public MyObject gridObject = null;
public MyViewModel ()
{
gridObject = new MyObject();
dataGrid = CreateDataGrid(gridObject);
}
private Datagrid CreateDataGrid(object objectName) {}
}
After this I want to bind the grid to the XAML. Is it possible or is there a better way of doing this, rather then a DataGrid? I want the functionality built into the datagrid like moving columns, mark specific rows etc..
You shouldn't create a DataGrid in a view model. This is no MVVM. You create it in the view.
If you don't want the DataGrid to autogenerate the columns for you, you should set its AutoGenerateColumns property to false.
You could then define the columns you want yourself, either in the XAML markup:
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="..." Binding="{Binding Property}" />
</DataGrid.Columns>
</DataGrid>
Or programmatically:
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "header...", Binding = new Binding("Property") });
The view model should return an IEnumerable<T> from a public property that the DataGrid in the view binds to:
<DataGrid ItemsSource="{Binding ViewModelCollection}" ...
Make sure that you set the DataContext of the view to an instance of the view model for the binding to work:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}

GridViewItem IsSelected binding in UWP App

We have a requirement to display the images in a GridView incrementally. So to find the selected items in GridView , IsSelected property of the GridView item has bind with corresponding binding objects property of CLR object (property of the GridView's ItemSource type). Since, UWP does not support RelativeSouce and setter binding in style, so after doing some search on internet we found the below code.
public class GridViewEx : GridView
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var gridItem = element as GridViewItem;
var binding = new Binding { Mode = BindingMode.TwoWay, Source = item, Path = new PropertyPath("IsSelected") };
gridItem.SetBinding(SelectorItem.IsSelectedProperty, binding);
}
}
But it seems that there is a flaw with the above approach. Whenever the page is being scrolled down to load next set of photos then the previous selected items are losing their selection.
Is anyone has experienced this issue before or any suggestions to solve the above problem?
Move IsSelected to the model class and bind it to a user control. The user control actually is the cell and will be put in your data template.
<GridView.ItemTemplate>
<DataTemplate>
<controls:MyCustomControl IsSelected="{Binding IsSelected}"/>
</DataTemplate>
</GridView.ItemTemplate>
In MyCustomControl, you will handle difference visual state to show the selected status of item. This way your ViewModel dont have to "know" the list at all, when you need just get list of selected item from your list of models.

WPF, EF: Show and save ordering of rows in datagrid, mvvm

My users want to be able to re-arrange the rows in some datagrids and other list controls. There is already an existing database with a SortOrder column (integer). I use entity framework 6.1. My view needs to show the items ordered by this column, and any changes to the ordering must be saved to the database as well, when the user clicks "save" and context.SaveChanges is called
My best attempt so far is to add a SortOrder column to my datagrid and sort by it (and I intend to make it invisible somehow...), attach PreviewKeyDown events for up/down that call commands in my viewmodel, that in turn update the SortOrder values. However, even if I do RaisePropertyChanged("MyDataGridItemSource"), the datagrid is not updated, and I've tried setting mode=twoway, NotifyBySource=true, NotifyByTarget=true. Re-setting the MyDataGridItemSource completely will update the value of the SortOrder column, but it will not re-arrange the rows according to it and I'll also lose my selection which is unwanted.
Do you have any good suggestions for mapping a database sorting column to controllers like this?
The easiest way to do it is to wrap the collection in a CollectionViewSource.
First of all you need to make sure that collection items implement INotifyPropertyChanged and PropertyChanged event is raised whenever the SortOrder property changes. Then define collection view source like this:
<CollectionViewSource x:Key="CollectionView" Source="{Binding Collection}" IsLiveSortingRequested="True">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="SortOrder" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
where xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase", and put it in a DataGrid's ancestor control's resources dictionary (for example in Window.Resources or UserControl.Resources). Lastly set the defined collection view source as items source of your DataGrid:
<DataGrid ItemsSource="{Binding Source={StaticResource CollectionView}}">
...
</DataGrid>
Now whenever SortOrder property is changed on any item the UI should be updated accordingly.
UPDATE
If items do not implement INotifyPropertyChanged the above solution won't work. You may want to consider creating a wrapper class that would expose necessary properties and implement INotifyPropertyChanged (this design pattern is commonly known as "decorator pattern"). However, if it is not an option, you can define collection view on your view model and bind to it instead to the collection itself, and manually refresh the view whenever any changes are made to items. Here's an example of how it might look like:
public IEnumerable<Item> Collection
{
get { ... }
set
{
//store the value in the backing field
if (value != null)
{
CollectionView = CollectionViewSource.GetDefaultView(value);
CollectionView.SortDescriptions.Add(new SortDescription
{
Direction = ListSortDirection.Ascending,
PropertyName = "SortOrder",
});
}
else
CollectionView = null;
}
}
public ICollectionView CollectionView
{
get { ... }
set
{
//store the value in the backing field and raise PropertyChanged
}
}
In XAML, bind to the collection view:
<DataGrid ItemsSource="{Binding CollectionView}">
...
</DataGrid>
Then, whenever you make changes to items, call CollectionView.Refresh() when you're done and UI will be updated.

wpf excel like grid editing?

In a project I have a very tricky requirement I don't know how to solve:
I have several datagrids in a single wpf window (I use MVVM) all binded to some collection in the linked ViewModel.
The customer wants to edit each of these grids, either within the grid or in a common textbox (like in excel).
I'm banging the head on how to do the latter. What I would do is bind the textbox with a property in the viewmodel, but when the value is changed there, I need to change the value in the original property binded with the datagrid cell accordingly. In other words, I need to know what collection and which property of that collection I need to change with the data in the textbox accordingly .
I tried several ways but with no luck.
Reflection? DependencyProperty? What else?
Any help?
Thank you
Assuming that you're using the built-in WPF DataGrid, you'll need to setup your grid similarly:
<DataGrid SelectionUnit="Cell" SelectionMode="Single" ItemsSource="{Binding Data}" SelectedCellsChanged="DataGrid_OnSelectedCellsChanged">
...
</DataGrid>
Also give your TextBox a name:
<TextBox x:Name="textBox" DockPanel.Dock="Top" />
In the code-behind, you'll need to manually wire up this event, since apparently the DataGrid doesn't allow you to bind to the selected item/cell/value when using SelectionUnit="Cell":
private void DataGrid_OnSelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
if (e.AddedCells.Count == 0)
this.textBox.SetBinding(TextBox.TextProperty, (string) null);
else
{
var selectedCell = e.AddedCells.First();
// Assumes your header is the same name as the field it's bound to
var binding = new Binding(selectedCell.Column.Header.ToString())
{
Mode = BindingMode.TwoWay,
Source = selectedCell.Item,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
this.textBox.SetBinding(TextBox.TextProperty, binding);
}
}
I tried getting this done without code-behind but after looking around it didn't seem like this was possible.
In addition to tencntraze answer I used this code to get te property bound to the cell
var property = (selectedCell.Column.ClipboardContentBinding as Binding).Path.Path;

CollectionViews in MVVM

Normally, to obtain the collection view of a control, I will call the following:
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(list.ItemsSource);
This is normally done in the code behind of the xaml file.
However, in MVVM, the ViewModel is not supposed to know about the existence of the View. How do I obtain the CollectionView of a control if I want to do it in MVVM fashion?
You can get the CollectionView in the ViewModel
1- You are having the Data Source of your list and you bind the item source of the list with this known Data Source.
2- Suppose DataSource is a DataTable named dt.
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(dt);
this will give you the CollectionView in ViewModel
you need to define the ItemsSource as a Property in the ViewModel like
public CollectionView _sourceForList;
public CollectionView SourceForList
{
get
{
return _sourceForList;
}
set
{
_sourceForList = value;
}
}
then in XAML you can bind this Property to List
<ListBox Margin="9,30,9,0"
Name="listBox1" ItemsSource="{Binding SourceForList}" }/>
and you can call like
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(SourceForList);
hope this helps
Retrieve the CollectionView in the code-behind of the xaml file (View). The MVVM pattern is not about eliminating the code-behind. It's about separation of concerns and testability.
The BookLibrary sample of the WPF Application Framework (WAF) shows how to work with the CollectionView for filtering in a MVVM application.

Categories