Bind to ObservableCollection specific Index - wpf

I have the next class:
public class GridViewControlModel : NotificationObject
{
DataTable datasource;
public DataTable Datasource
{
get
{
return datasource;
}
set
{
datasource = value;
RaisePropertyChanged("Datasource");
}
}
ObservableCollection<string> fieldsNames;
public ObservableCollection<string> FieldsNames
{
get
{
if (fieldsNames == null)
{
fieldsNames = new ObservableCollection<string>();
}
return fieldsNames;
}
set
{
fieldsNames = value;
RaisePropertyChanged("FieldsNames");
}
}
//and more properties
}
The property "FieldsNames" contains list of columns Headers that I want bind to GridView data column header.
In my ViewModel I created property - GridViewControlModel CurrentGridViewData and I want to bind my GridView columns headers to my CurrentGridViewData.FieldsNames[indexes]
for example
<dxg:GridColumn1 Header="{Binding CurrentGridViewData, Path=CurrentGridViewData.FieldsNames[0]}">
<dxg:GridColumn2 Header="{Binding CurrentGridViewData, Path=CurrentGridViewData.FieldsNames[1]}">
and so on...
Im working with DevExpress gridCointrol but Im don`t think its matter .
How can I bind property to specific ObservableCollection index?
I will explain what Im trying to do.
I have GridControl with many views.
When the user switch view the grid control need to change its entire structure (different number of columns, columns headers, column size, types and etc). I created a class which contains all the properties and settings for each view (this CurrentGridViewData class from my question) and when the user switch view I change the reference fo CurrentGridViewData to the right view instance and by binding I want to make all grid changes.
Is there a better way?
Thanks
Thanks
I think I found possible solution related to DevExpress GridControl.
Look at the next link
http://documentation.devexpress.com/#wpf/CustomDocument10121
What are you thinking? Is it better approach than using DataTemplate?
Thanks

My solution for you would be to create a DataTemplate for each of the model you want to bind to your DataGrid.
For example lets say you have 3 different DataTables: OrdersDataTable, CustomersDataTable and ProductsDataTable.
First thing would be to replace your DataGrid control by a simple ContentControl.
<ContentControl Content="{Binding DataTable}"/>
Then add and create those DataTemplate in your application ressources.
<DataTemplate DataType="{x:Type local:OrdersDataTable}">
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}" Header="ID"/>
<DataGridTextColumn Binding="{Binding Tax}" Header="Tax"/>
<DataGridTextColumn Binding="{Binding NetAmount}" Header="NetAmount"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CustomersDataTable}">
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}" Header="ID"/>
<DataGridTextColumn Binding="{Binding FirstName}" Header="First name"/>
<DataGridTextColumn Binding="{Binding LastName}" Header="Last name"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsDataTable}">
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding ID}" Header="ID"/>
<DataGridTextColumn Binding="{Binding Name}" Header="Name"/>
<DataGridTextColumn Binding="{Binding Quantity}" Header="Quantity"/>
<DataGridTextColumn Binding="{Binding UnitPrice}" Header="UnitPrice"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
This way, when you will bind your DataTable to the content control, it will lookup for the concrete DataTable type and crawl the application resources to find a corresponding DataTemplate to display your DataTable.
I recommend to use a Style for the DataGrid control so you don't have to reproduce the style in all the DataTemplate.
NB: The DataGrid.ItemsSource might not be correct for you, just adapt it.

Related

WPF Datagrid select a value from a cell in a certain row

I have a DataGrid with 3 columns(id, User, MachineID) I am looking to get the machine Id from this grid. Preferably I would like to use a check box to select multiple rows, but I just need to get one row at a time sorted.
<DataGrid Grid.Column="0" Grid.Row="0"
DockPanel.Dock="Bottom" AutoGenerateColumns="False"
ItemsSource="{Binding Path=UserList}" SelectionMode="Single"
SelectedItem="{Binding SelectedCpuID, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding UserID}"></DataGridTextColumn>
<DataGridTextColumn Width="200" Header="User" Binding="{Binding UserName}"></DataGridTextColumn>
<DataGridTextColumn Width="210" Header="Machine ID" Binding="{Binding MachineID}"></DataGridTextColumn>
It is bound to the view model but I get the object name instead of the contents of the row(application.Users). I have tried SelectedItem="{Binding SelectedCpuID.MachineID, Mode=TwoWay}"> which also did not work. I am quite new to WPF and data binding so I have been trying to work off another program that we have produced here which does a similar thing but uses combo boxes and it doesn't seem to work the same with the datagrid.
Any one know how to get this to work please let me know.
Thanks,
Sam
You are probably just missing the COLUMNS of the data grid for the display. The DataGrid's ItemsSource, such as a DataTable, or a Collection (List<>, or other IEnumerable) of all items you want to present to the user. From there, you need to define each column you want to display and give those characteristics too (width, font, coloring, whatever). The binding of each column is a path as it would be found on each entry from the UserList.
Then,
<DataGrid
ItemsSource="{Binding UserList}"
AutoGenerateColumns=False >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" />
<DataGridTextColumn Header="User" Binding="{Binding Path=User}" />
<DataGridTextColumn Header="Machine" Binding="{Binding Path=MachineID}" />
</DataGrid.Columns>
</DataGrid>
Now, your UserList source. If it is such as a List(), then each property should be available via getter/setter, such as...
public class SomeUserClass()
{
public string Id {get; set;}
public string User {get; set;}
public string MachineID {get; set; }
}

WPF DataGrid 'Refresh' is not allowed during an AddNew or EditItem transaction mvvm

I have the following grid
<DataGrid
x:Name="TablesDataGrid"
Grid.Column="0"
Grid.Row="1"
ItemsSource="{Binding FilteredModels.View}"
AlternationCount="2"
AutoGenerateColumns="False"
CanUserSortColumns="True"
CanUserReorderColumns="False"
CanUserDeleteRows="False"
CanUserAddRows="False"
SelectionMode="Extended"
IsReadOnly="False"
SelectionUnit="FullRow"
RowHeight="25"
HorizontalAlignment="Stretch"
ColumnWidth="Auto">
<DataGrid.Columns >
<DataGridCheckBoxColumn Width="*" Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" IsReadOnly="False">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.CheckAll}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTextColumn Header="Source Table" Binding="{Binding SourceTableFullName}" Width="4*"></DataGridTextColumn>
<DataGridTextColumn Header="EDW Schema" Binding="{Binding SchemaName}" Width="2*"></DataGridTextColumn>
<DataGridTextColumn Header="EDW Table" Binding="{Binding TableName}" Width="4*"></DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
and then i have a seachCommand with performs the search on the collectionViewSource FilteredModels in the viewmodel and then calls
this.FilteredModels.View.Refresh();
when a user checks a few of the checkboxes and sends the grid into editmode and then performs a search we get the following error
WPF DataGrid 'Refresh' is not allowed during an AddNew or EditItem transaction
is there a way to force the grid out of edit mode when a check box is checked or maybe even when the seach button is clicked or some other fix for this?
thanks!
I know its too late to answer...but for someone who is looking for answer
use cancelEdit or commitEdit method two times in a sequence like this
//for commit
this.datagrid_layers.CommitEdit();
this.datagrid_layers.CommitEdit();
//for cancel
this.datagrid_layers.CancelEdit();
this.datagrid_layers.CancelEdit();
you should be able to cast the selected item to IEditableObject and call EndEdit on it, or call the grids CancelEdit method.
There is a clean MVVM solution to the problem. First off, your ViewModels must implement IEditableObject interface (no-op should be enough). That, however, is not enough since the DataGrid will not listen to IEditableObject.CancelEdit.
Another problem is, that neither ICollectionView nor IEditableCollectionView implement the other one. While only ICollectionView can refresh, only IEditableCollectionView can commit/cancel. Luckily collection view returned by CollectionViewSource.GetDefaultView implements both:
// ViewModel.cs
public class ItemVM : IEditableObject, INotifyPropertyChanged { }
public class ModuleVM : INotifyPropertyChanged {
ICollectionView Items { get; }
public ModuleVM(ObservableCollection<ItemVM> items) {
Items = CollectionViewSource.GetDefaultView(items);
}
public void RefreshSafely() {
((IEditableCollectionView)Items).CancelEdit(); // alterantively, CommitEdit()
Items.Refresh();
}
}
Or in other words, you can cast ICollectionView to IEditableCollectionView and call CancelEdit() first.

Silverlight - MVVM: Datagrid bound to ICollectionView filled with "ChildViewModel" wont refresh data

I couldn't find a similar case yet and so I struggle on. I'm quite a greenhorn with Silverlight and struggle with the refresh of a ICollectionView. The Refresh-Method won't actually refresh the data in my datagrid although they're changed (see it in debugger and after sorting the datagrid a few times it will finally reflect the changes). I'm afraid I completely messed up the whole construct of my application. I think the problem is somehow related to my "MainViewModel-ChildViewModel" princip I implemented.
Here is my construct:
In my Main Page i added a "Main Viewmodel" as resource.
<UserControl.Resources>
<vm:WorkingBasketViewModel x:Key="VMMain"/>
</UserControl.Resources>
the Grid LayoutRoot then sets it's datacontext to this viewmodel:
<Grid x:Name="LayoutRoot" DataContext="{StaticResource VMMain}" Margin="20">
........// all the content
</Grid>
In the "Main Viewmodel" I define a ObservableCollection which holds the data that will be displayed in a datagrid in form of a CollectionViewSource.
the ObservableCollection items are based on a "ChildViewModel" that represents the actual data and it's logic for each datarow.
Means: Each item that is added to the ObservableCollection is of the type of the "ChildViewModel".
I designed it that way because I am going to display several "detail-pages" (based on chosen Function or on double click of a cell) that will then allow to view, modify and work on the data in another usercontrol. Direct modification in the datagrid is not allowed. That way around I only need to pass the childviewmodel to the next page (or usercontrol) and the data and its logic are passed on.
private readonly ObservableCollection<childViewModel> _requestList = new ObservableCollection<childViewModel>(); // saves list of "childviewmodel-items"
private readonly ICollectionView _requestCollectionView; // ICollectionView for _requestlist-Collection.
// In the contstructor of the "Main Viewmodel"
var cvs = new CollectionViewSource {Source = _requestList};
cvs.SortDescriptions.Add(new SortDescription("RPI_Priority", ListSortDirection.Ascending));
cvs.SortDescriptions.Add(new SortDescription("REQ_TestingDate", ListSortDirection.Ascending));
_requestCollectionView = cvs.View;
LoadData(); // db-fetch (entity framework)
/// <summary>
/// Binding to DataGrid!
/// </summary>
public ICollectionView Requests //-> BINDING TO DATAGRID!
{
get
{
return _requestCollectionView;
}
}
In the Completed eventhandler of the db-fetch fill the observablecollection with the childviewmodel
private void requests_requestLoadingComplete(object sender, EntityResultsArgs<REQ_Request> e)
{
if (!e.HasError)
{
//Fire Event on UI Thread
Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
{
var o = e.Results.OrderBy(r => r.REQ_TestingDate);
//clear request list
_requestList.Clear();
// add requests to collectionview
foreach (REQ_Request r in o)
{
// for each record generate a Childviewmodel entry and add it to the observable collection
_requestList.Add(new childviewmodel(r));
}
});
}
else
{
// notify if there is any error
reportError(this,new ResultsArgs(e.Error));
}
RaiseVMStateChanged();
}
I further have a datagrid on the mainpage that is bound to this ICollectionView. The itemsource is to the list of "ChildViewModel". It's Properties are bound it:
<sdk:DataGrid AutoGenerateColumns="False" Grid.Row="1" ItemsSource="{Binding Path=Requests}" SelectionMode="Single">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="ID" Binding="{Binding REQ_ID}" Width="40" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Applikationsname" Binding="{Binding REQ_ApplicationName}" Width="250" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Typ" Binding="{Binding RET_Type}" Width="70" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Prio" Binding="{Binding RPI_Priority}" Width="70" IsReadOnly="true" />
<sdk:DataGridTextColumn Header="Status" Binding="{Binding RST_Status}" Width="70" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Sprache" Binding="{Binding SWL_Language}" Width="70" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Version" Binding="{Binding REQ_Version}" Width="70" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Betriebssystem" Binding="{Binding SOS_OS}" Width="150" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="DA" Binding="{Binding Dienstabteilungen}" Width="150" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="AV" Binding="{Binding AV_Fullname}" Width="150" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Paketierer" Binding="{Binding Paketierer_Fullname}" Width="150" IsReadOnly="true"/>
<sdk:DataGridTextColumn Header="Paketierer QS" Binding="{Binding PaketiererQS_Fullname}" Width="150" IsReadOnly="true" />
<sdk:DataGridTextColumn Header="Abnahmetermin" Binding="{Binding REQ_TestingDate}" Width="150" IsReadOnly="true" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
Now all of this works fine and smoothly.
On click of a Function-Button I open another usercontrol that is initialised with an instanze of the "Childviewmodel" or an inherit of it.
(with some function that is a childwindow with some other function it shows a usercontrol that displays all the details etc)
for example childwindow:
ShowChildWindow(new PkgRequestDataControl(_vm.CurrentRequest)); --> PkgRequestDataControl inherits from childviewmodel. _vm.CurrentRequest is one single instance of "childviewmodel" that is given
Now I modify the date in this childwindow an return to the mainpage.
On return I call refresh on the collectionView (Requests.Refresh();) -> But the data won't refresh.
Well..sometimes it does but most of the time it does not until i sorted the changed coloum of the datagrid 2-3 times (click on the header to sort und sort..and sort)
What am I doing wrong? Anybody can help out? Is the whole construct messy?
Cheers
Elime
I'm not sure exactly which implementation of ICollectionView you're using, but in general ICollectionView.Refresh() only refreshes the View property taking filtering, sorting and grouping into account. For your UI to realize this change, you still need to implement INotifyPropertyChanged and raise a PropertyChanged event after calling Refresh().

Bind linq to DataGridComboBoxColumn

I am using wpftoolkit from wpf.codeplex.com . While binding a data table(through linq object list) there is a collumn that contains a reference of another table content as a foreign key. So, generally, currently, on that column in datagrid, i want to show a combo box containing the element of another table and selelted item should be from my concerned table. Here is what I am writing:
<dg:DataGrid x:Name="UsersGrid" AutoGenerateColumns="False" CellEditEnding="UsersGrid_CellEditEnding" RowEditEnding="UsersGrid_RowEditEnding" PreviewKeyDown="UsersGrid_PreviewKeyDown">
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Binding="{Binding Path=Id}" Header="Id" Visibility="Hidden" />
<dg:DataGridTextColumn Binding="{Binding Path=Username}" Header="User Name" />
<dg:DataGridTextColumn Binding="{Binding Path=Password}" Header="Password" />
<dg:DataGridTextColumn Binding="{Binding Path=RoleId}" Header="Role ID" />
<dg:DataGridComboBoxColumn x:Name="Roles" ItemsSource="{Binding Path=TVRole}">
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
For making the binding, the code behind file contains as the following code:
UsersGrid.ItemsSource = UserManager.GetAllUsers();
Problem is that, I am now don't know what to place in the "ItemsSource" property to achieve what i want for the DataGridComboBoxColumn column . Can anyone please help me? Again mentioning, i want to bind it with linq object.
There is a strange bug with a DataGridComboBoxColumn: the ItemsSource property doesn't support advanced bindings. But you can use DataGridTemplateColumn, and code will be like this:
<DataGrid ItemsSource="{Binding Users}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Id}" Header="Id" Visibility="Hidden" />
<DataGridTextColumn Binding="{Binding Path=Username}" Header="User Name" />
<DataGridTextColumn Binding="{Binding Path=Password}" Header="Password" />
<DataGridTextColumn Binding="{Binding Path=RoleId}" Header="Role ID" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Roles, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"
DisplayMemberPath="Title" SelectedValuePath="Id" SelectedValue="{Binding RoleId, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And add this code to the constructor of the View:
var model = new MainViewModel() { Users = UserManager.GetAllUsers(), Roles = UserManager.GetAllRoles() };
this.DataContext = model;
Where the MainViewModel is an auxiliary class:
public class MainViewModel
{
public List<Role> Roles { get; set; }
public List<User> Users { get; set; }
}
Another way is using a list of UserViewModel instead of a list of User, where each viewmodel class contains the list of roles, so it will be possible to use a correct binding. But it is a more complicated solution than above-mentioned one.

Databinding LINQ query results (with join tables) to DataGrid

In my WPF 4 desktop-based application, I have an LINQ query that makes a join with two tables.
Here is code for function that returns results of LINQ query:
public IList GetTableData()
{
IList dataList = (from dWorker in App.glidusContext.tbl_workers
join d2 in App.glidusContext.tbl_workers_has_tbl_events
on dWorker.workerID equals d2.workerID
where dWorker.workerTZ == strSplit
select new { dWorker, d2 }).ToList();
return data list;
}
Now, all what I rest to do is data bind results of this LINQ query to DataGrid:
private IList currentData; //class property
public void AssignTableContentToDataContext() {
currentData = GetTableData();
this.ContentDataGrid.DataContext = currentData;
}
And here is an XAML-code of DataGrid:
<DataGrid x:Name="ContentDataGrid"
Style="{StaticResource Body_Content_DataGrid}"
CellStyle="{StaticResource Body_Content_DataGrid_Centering}"
ItemsSource="{Binding}"
KeyboardNavigation.TabIndex="8"
SelectionChanged="ContentDataGrid_SelectionChanged" DataContext="{Binding}">
<!-- PreviewKeyDown="DBRecord_Delete_PreviewKeyDown" -->
<!-- CellEditEnding="ContentDataGrid_CellEditEnding" -->
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_WorkerID}"
Width="130"
IsReadOnly="True"
Binding="{Binding Path=workerID}" />
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_workerTZ}"
Width="130"
Binding="{Binding Path=workerTZ}" />
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_WorkerFirstName}"
Width="130"
Binding="{Binding Path=workerFirstName}" />
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_WorkerLastName}"
Width="130"
Binding="{Binding Path=workerLastName}" />
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_EventID}"
Width="130"
Binding="{Binding Path=eventID}" />
<DataGridTextColumn Header="{x:Static res:Resources.WinSafetyByEmployee_DataGrid_ColHead_EventName}"
Width="*"
Binding="{Binding Path=eventName}" />
</DataGrid.Columns>
</DataGrid>
After debug I got the following results:
My query returns rows with results
My DataGrid shows lines, according to number of lines in the query result, but they are blank!
How can I data bind this data and show them in DataGrid?
P.S. In case of more simple query such as select * from _table_ everything works fine and DataGrid shows data, but when I use more complex queries with more than one table (make join) I can't databind it correctly.
LINQ-join works different from SQL-join. It doesn't merge different sources in one plain source.
So change each binding in the DataGrid like in this example:
{Binding Path=workerID}
for
{Binding Path=dWorker.workerID}
Or d2.workerID, I don't know the properties of these classes, but the mistake is in the bindings.
And don't forget to set IsReadOnly="True" in the DataGrid, because otherwise it will raise an exception if to start editing.
You dont have to use entity alias in your binding clause. Just put the column name on the Binding clause
<DataGridTextColumn Width="200" Header="CPF" IsReadOnly="True" Binding="{Binding Path=NumeroDoc}">
Remove .ToList() from your code, returning a IEnumerable object
public static IEnumerable getQueryTable()
{
var dataList = (from c in App.DbContext.Cliente
join d in App.DbContext.DadosDocumento
on c.IDCliente equals d.IDCliente
select new { c.IDCliente, c.Nome, c.DataCadastro, d.NumeroDoc });
return dataList;
}
And fill the datagrid using ItemsSource property:
dgDados.ItemsSource = DBHelper.getQueryTable();
It works fine for me.

Resources