EF6: SaveChanges overriding, changed entities not updating in WPF - wpf

I have an overridden SaveChanges in my EF6 DbContext, where I set some dates and users. These changes are being saved to the database ok, but I have to quit and reopen my WPF form before they're visible there.
The SaveChanges override is:
//make sure we get all the changed objects
ChangeTracker.DetectChanges();
ObjectContext ctx = ((IObjectContextAdapter) this).ObjectContext;
//get the current user name...
//TODO needs checking that this works when via service.
string userID = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
userID = userID.Substring(userID.IndexOf('\\') + 1); //remove domain
foreach (var dbEntityEntry in ctx.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Where(e => e.Entity is IAuditInfo))
{
switch (dbEntityEntry.State)
{
case EntityState.Added:
((IAuditInfo) dbEntityEntry.Entity).CreatedOn = DateTime.Now;
((IAuditInfo) dbEntityEntry.Entity).CreatedBy = userID;
break;
case EntityState.Modified:
((IAuditInfo) dbEntityEntry.Entity).LastUpdatedOn = DateTime.Now;
((IAuditInfo) dbEntityEntry.Entity).LastUpdatedBy = userID;
break;
case EntityState.Deleted:
case EntityState.Detached:
case EntityState.Unchanged:
default:
break;
}
}
ctx.SaveChanges(SaveOptions.None);
return base.SaveChanges();
My WPF XAML:
<Page.Resources>
<CollectionViewSource x:Key="actionStatusesViewSource"
d:DesignSource="{d:DesignInstance my:ActionStatus, CreateList=True}" />
</Page.Resources>
<Grid DataContext="{StaticResource actionStatusesViewSource}"
Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<DataGrid Grid.Row ="1"
AutoGenerateColumns="False"
EnableRowVirtualization="True"
ItemsSource="{Binding}"
Name="actionStatusesDataGrid"
RowDetailsVisibilityMode="VisibleWhenSelected"
VerticalAlignment="Top"
ClipboardCopyMode="IncludeHeader">
<DataGrid.Columns>
<DataGridTextColumn x:Name="idColumn"
Binding="{Binding Path=Id, Mode=TwoWay}"
Header="Id"
Width="SizeToHeader" />
<DataGridTextColumn x:Name="nameColumn"
Binding="{Binding Path=Name, Mode=TwoWay}"
Header="Name"
Width="256" />
<DataGridTemplateColumn x:Name="validFromColumn"
Header="Valid From"
Width="128">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Path=ValidFrom, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="validToColumn"
Header="Valid To"
Width="128">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Path=ValidTo, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="lastUpdatedByColumn"
Binding="{Binding Path=LastUpdatedBy, Mode=TwoWay}"
Header="Updated By"
Width="SizeToHeader" />
<DataGridTextColumn x:Name="lastUpdatedOnColumn"
Binding="{Binding Path=LastUpdatedOn, Mode=TwoWay, StringFormat=\{0:dd/MM/yyyy HH:mm\}}"
Header="Updated On"
Width="SizeToCells" />
<DataGridTextColumn x:Name="createdByColumn"
Binding="{Binding Path=CreatedBy, Mode=TwoWay}"
Header="Created By"
Width="SizeToHeader" />
<DataGridTextColumn x:Name="createdOnColumn"
Binding="{Binding Path=CreatedOn, Mode=TwoWay, StringFormat=\{0:dd/MM/yyyy HH:mm\}}"
Header="Created On"
Width="SizeToCells" />
</DataGrid.Columns>
</DataGrid>
And finally my load and save code:
private RegRiskContext context; //our model context (via the service)
private DataServiceCollection<ActionStatus> actionStatusBinding; //our bound collection
private CollectionViewSource viewSource; //the view source for the collection
private delegate void OperationResultCallback(); //delegate for the dispatcher invokes
public AdminActionStatus()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
//get the CollectionViewSource object
viewSource = ((CollectionViewSource) (this.FindResource("actionStatusesViewSource")));
try
{
UIHelper.ProgressBarRun(true);
//initialise the context
context = new RegRiskContext(new Uri(RegRiskSettings.Default.ServiceURL));
//create a query ready for the async operation
DataServiceQuery<ActionStatus> dsq = context.ActionStatuses;
try
{
dsq.BeginExecute(OnQueryCompleted, dsq);
}
catch (DataServiceClientException ex)
{
MessageBox.Show(ex.ToString());
}
/* synchronous version
* note the freeze when opening the window
var q = context.ActionStatuses.OrderBy(f => f.Id);
DataServiceCollection<ActionStatus> actionStatuses = new DataServiceCollection<ActionStatus>(q);
viewSource.Source = actionStatuses;
*/
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void OnQueryCompleted(IAsyncResult result)
{
// Get the original query object from the state cache.
DataServiceQuery<ActionStatus> query = (DataServiceQuery<ActionStatus>) result.AsyncState;
//use Dispatcher to ensure we're on the UI thread!
this.Dispatcher.BeginInvoke(new OperationResultCallback(delegate
{
try
{
//instantiate the binding collection using results of the query
actionStatusBinding = new DataServiceCollection<ActionStatus>(query.EndExecute(result));
//set the Source to the collection
viewSource.Source = actionStatusBinding;
UIHelper.ProgressBarRun(false);
}
catch (DataServiceRequestException ex)
{
MessageBox.Show(ex.ToString());
}
}), null);
}
private void saveButton_Click(object sender, RoutedEventArgs e)
{
try
{
UIHelper.ProgressBarRun(true);
context.BeginSaveChanges(OnSaveChangesCompleted, null);
}
catch (DataServiceClientException ex)
{
MessageBox.Show(ex.ToString());
}
}
private void OnSaveChangesCompleted(IAsyncResult result)
{
// Use the Dispatcher to ensure that the operation returns in the UI thread.
this.Dispatcher.BeginInvoke(new OperationResultCallback(delegate
{
try
{
// Complete the save changes operation.
context.EndSaveChanges(result);
viewSource.View.Refresh();
UIHelper.ProgressBarRun(false);
}
catch (DataServiceRequestException ex)
{
MessageBox.Show(ex.ToString());
}
}), null);
}
I don't know if its because my SaveChanges override needs to notify somehow? or if its the WPF thats wrong?
There is a WCF DataServices layer between the WPF and the EF, but that looks 'straightforward' enough, and I can't see what I could even change there.

Found a solution, not entirely sure why it fixes it though, so any expanded explanation would be gratefully received.
My WCF DataService class is simple:
public class RegRiskService : EntityFrameworkDataService<BOI.RegRisk.Model.RegRiskContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
// TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
//snip access stuff
}
}
It was the protocol version, it was defaulted to v2, changing it to v3 seems to have resolved the issue. My WPF datagrid now updated with the changes made in the SaveChanges override.
Again there's a caveat for anybody with the same original issue, I've been looking at this issue for a while, so there is a chance there was something else I changed that was the actual root cause!

Related

how to set the width of datatable column and how to set the color of datatable column upon some condition

IN xaml.cs file(WPF Application) I have created a DataTable with 3 columns
Wanted to set the second column's width in xaml.cs only.
Also, want to set the second columns first row background color to blue(only for the cell which is in first row and 2nd column).
Have created 3 columns as :
DataTable dt= new DataTable();
dt.Columns.Add(new DataColumn("ABC");
Similarly, have added 2 more columns.
Want to set the second columns width
I am not fairly certain, if this is what you are looking for, but it is what I would do
First: let's say you have created a basic DataTable and filled it with some values, like this:
DataTable dt = new DataTable();
dt.Columns.Add("Key", typeof(int));
dt.Columns.Add("Material", typeof(string));
dt.Columns.Add("Price per Kilo", typeof(int));
dt.Rows.Add(1, "CobbleStone", 34);
dt.Rows.Add(2, "Wooden Planks", 12);
dt.Rows.Add(3, "Iron Ingots", 56);
which looks like this in the debugger:
Second: Get some VisualElement to display your Data. I'd suggest using a DataGrid for. So go your your MainWindows.xaml and add a DataGrid to your Grid with 3 DataGridTextColumns like this:
<DataGrid>
</DataGrid>
Since we want to add custiom properties to our Columns, we have to add AutoGenerateColumns="False" to our DataGrid, if we don't the DataGrid will automatically generate its columns based on its ItemsSource. Since we won't get any autogenerated Columns now, we also have to add 3 Columns resembling the 3 columns from our DataTable:
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" />
<DataGridTextColumn Header="Material" />
<DataGridTextColumn Header="Price per Kilo" />
</DataGrid.Columns>
</DataGrid>
Third: Next we have to set the ItemsSource of our DataGrid. Unfortunately a DataGrid can't process a DataTable, so we first have to convert our DataTable into something the DataGrid can read. Let's generate a new Class for this and call it MaterialModel, which looks like this:
using System.ComponentModel;
using System.Runtime.CompilerServices;
class Model : INotifyPropertyChanged
{
private int m_Key;
public int Key
{
get
{
return m_Key;
}
set
{
m_Key = value;
OnPropertyChanged("Key");
}
}
private string m_Name;
public string Name
{
get
{
return m_Name;
}
set
{
m_Name = value;
OnPropertyChanged("Name");
}
}
private int m_Price;
public int Price
{
get
{
return m_Price;
}
set
{
m_Price = value;
OnPropertyChanged("Price");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
It has Properties and a PropertyChangedEventHandler, which will notify your VisualElement when the Property changes.
Fourth: The DataGrid doesn't accept DataTables, but it accepts Lists and ObserableCollections. Use a List, if you don't want to ever add/change your items at runtime. I'll use an ObserableCollection, which neeeds using System.Collections.ObjectModel; to work.
Create a Property of your List and add a PropertyChangedEventHandler to MainWindow.
public partial class MainWindow : Window
{
private ObservableCollection<MaterialModel> m_MaterialList;
public ObservableCollection<MaterialModel> MaterialList
{
get
{
return m_MaterialList;
}
set
{
m_MaterialList = value;
OnPropertyChanged("MaterialList");
}
}
public MainWindow()
{
// [...]
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The next step would be to convert your DataTable into a ObservableCollection, so iterate through your DataTable and Convert each Row to one of your Models, like this:
MaterialList = new ObservableCollection<MaterialModel>();
foreach(DataRow row in dt.Rows)
{
MaterialModel model = new MaterialModel
{
Key = int.Parse(row["Key"].ToString()),
Name = row["Material"].ToString(),
Price = int.Parse(row["Price per Kilo"].ToString()),
};
MaterialList.Add(model);
}
Fivth: Your List is filled with Models, the next step would be to tell your DataGrid how to use your List. First, bind your List to the ItemsSource auf your DataGrid, then bind each DataGridTextColumn to one of the Properties in your MaterialModel, like this:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MaterialList}">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Key}" />
<DataGridTextColumn Header="Material" Binding="{Binding Name}" />
<DataGridTextColumn Header="Price per Kilo" Binding="{Binding Price}" />
</DataGrid.Columns>
</DataGrid>
and you'll see the DataGrid works:
Sixth: The last step is to actually set the properties of your columns, which is pretty easy, your Requirements would look something like this:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MaterialList}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Key" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Key}" Background="LightBlue"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Material" Binding="{Binding Name}" Width="300" />
<DataGridTextColumn Header="Price per Kilo" Binding="{Binding Price}" />
</DataGrid.Columns>
</DataGrid>
I haven't found a way to create a DataGrid completely in Code behind as you wanted, but this would be cosnidered bad practice anyway. WPF is designed to use this connection between xaml und c#.
If you want to manage your column properties in c# anyways, this would be a proper way to do it:
in your MainWindow.xaml.cs:
private double m_SecondColumnWidth;
public double SecondColumnWidth
{
get
{
return m_SecondColumnWidth;
}
set
{
m_SecondColumnWidth = value;
OnPropertyChanged("SecondColumnWidth");
}
}
public MainWindow()
{
SecondColumnWidth = 300;
}
XAML:
<!-- right beneath your Grid -->
<Grid.Resources>
<local:ViewModel x:Key="viewModel" />
</Grid.Resources>
<DataGridTextColumn Header="Material" Binding="{Binding Name}" Width="{Binding Source={StaticResource viewModel}, Path=SecondColumnWidth}" />
This isn't exactly what you wanted, but I hope it helps any way.

WPF DataGridComboBoxColumn binding

im having some trouble when trying to use a DataGridComboBoxColumn to update my entity framework
I have a datagrid that I am binding to a Custom Model (FunctionPrinterLookupModel), which is basically a lookup between Printers and Functions around the building. The Functions are all static, but I would like users to be able to select which printer they use for the function.
<DataGrid Grid.Row="1" x:Name="gridLookup" AutoGenerateColumns="False" Width="500" RowEditEnding="gridLookup_RowEditEnding" Margin="20">
<DataGrid.DataContext>
<Models:Printer/>
</DataGrid.DataContext>
<DataGrid.Columns>
<DataGridTextColumn Header="Function" Width="*" IsReadOnly="True" Binding="{Binding FunctionName}"/>
<!--<DataGridTextColumn Header="Printer" Width="*" Binding="{Binding PrinterName, UpdateSourceTrigger=PropertyChanged}"/>-->
<DataGridComboBoxColumn x:Name="ddlPrinters" Header="Printer" Width="*" SelectedValueBinding="{Binding PrinterID, Mode=TwoWay}" SelectedValuePath="{Binding PrinterID, Mode=TwoWay}" DisplayMemberPath="{Binding PrinterName}"/>
</DataGrid.Columns>
</DataGrid>
private void gridPrinters_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (e.EditAction == DataGridEditAction.Commit)
{
Printer printer = (Printer)e.Row.Item;
if (printer.PrinterID != 0)
{
Printer printerDB = context.Printers.Where(s => s.PrinterID == printer.PrinterID).Single();
printerDB.PrinterName = printer.PrinterName;
context.SaveChanges();
}
else
{
Printer newPrinter = new Printer()
{
PrinterName = printer.PrinterName
};
context.Printers.Add(newPrinter);
context.SaveChanges();
}
}
RefreshPrintersGrid();
}
I am binding the DataGridComboBoxColumn in the code behind to an EF model containing list of Printers.
When the value has been selected and we trigger the RowEditEnding function, the value of the combobox is not updated in the FunctionPrinterLookupModel model. I feel like im tying myself in knots here and havent been able to find a solution that works from my hours of googling. Can any one help straighten me out?
You would be better off binding the combobox items source to a property in your ViewModel. Then bind the selected printer and in the ViewModel take action when the property changes.
<DataGridComboBoxColumn x:Name="ddlPrinters" Header="Printer" Width="*" ItemsSource="{Binding PrinterList}" SelectedItem="{Binding SelectedPrinter, Mode=TwoWay}" SelectedValuePath="PrinterID" DisplayMemberPath="PrinterName"/>
In ViewModel
Private PrinterInfo _SelectedPrinter { get; set; }
Publuc PrinterInfo SelectedPrinter
{
get
{
return _SelectedPrinter;
}
set
{
_SelectedPrinter = value;
//save value to db or other actions
}
}

Checkbox column for multiple rows when shift keyboard modifier is applied

I have a view model that contains an "IsChecked" property. This property is bound to a DataGrid RowHeaderTemplate.
I'd like to be able to hold down the shift + ctrl keyboard modifiers to allow for multiple checkbox values to be updated.
For example: If you check the checkbox in the first row, hold down shift, and check the checkbox in the 5th row; rows 1-5 would then have a checked checkbox.
Below is the row header template:
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<CheckBox VerticalAlignment="Center"
VerticalContentAlignment="Center"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</DataGrid.RowHeaderTemplate>
I've tried several combinations of capturing PreviewMouseLeftButtonDown and KeyPress events, but I'm not sure this is even possible with a bound property.
I've come up with a solution by listening to the IsChecked property changed. This does the trick for my needs.
private void OnItemOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
ItemViewModel newCheckedItem = sender as ItemViewModel;
int newCheckedItemIndex = _items.IndexOf(newCheckedItem);
if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && newCheckedItemIndex != _lastCheckedItemIndex)
{
int start = Math.Min(_lastCheckedItemIndex, newCheckedItemIndex);
int end = Math.Max(_lastCheckedItemIndex, newCheckedItemIndex);
int countToTake = end - start;
var itemsToCheck = _items.Skip(start).Take(countToTake).ToList();
foreach (ItemViewModel itemToCheck in itemsToCheck)
{
// Unhook the property changed event so we don't enter this method again
itemToCheck.PropertyChanged -= OnItemOnPropertyChanged;
itemToCheck.IsChecked = true;
// Re-register the proeprty changed event so that if we're unchecked/re-checked, we can respond.
itemToCheck.PropertyChanged += OnItemOnPropertyChanged;
}
_lastCheckedItemIndex = -1;
}
else
{
_lastCheckedItemIndex = newCheckedItemIndex;
}
}
}
I have faced the same problem and implemented solution somehow similar to what Michael G presented, but more straightforward. Frankly speaking I could not make the OnPropertyChanged event to work correctly so I developed the following solution:
XAML:
<DataGrid x:Name="_dg_DatasZones" Grid.Row="4" ItemsSource="{Binding}" LoadingRow="_dg_DatasZones_LoadingRow" Style="{StaticResource StandardGrid}" IsReadOnly="False"
SelectionMode="Single" >
<DataGrid.Columns>
<DataGridTemplateColumn CommonClasses:DataGridTemplateColumnStyleExtensions.Style="{StaticResource DataGridColumnIcon}">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Name="_CheckBox_SelectAllZone" Click="_CheckBox_SelectAllZone_Click" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" Checked="Zone_Checked" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Foreground="Black" Width="50" Binding="{Binding Id}" ElementStyle="{StaticResource DataGridTextStyle}" Header="ID" HeaderTemplate="{StaticResource DataGridHeaderStyle}" IsReadOnly="True" />
<DataGridTextColumn Foreground="Black" MinWidth="100" Binding="{Binding Name}" ElementStyle="{StaticResource DataGridTextStyle}" Header="ZoneName" HeaderTemplate="{StaticResource DataGridHeaderStyle}" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
Make sure that SelectionMode="Single"
C# code:
int _lastSelectedZoneIndex = -1;
private void Zone_Checked(object sender, RoutedEventArgs e)
{
int index = _dg_DatasZones.SelectedIndex;
if ( (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
&& index != _lastSelectedZoneIndex && _lastSelectedZoneIndex >= 0)
{
CheckBox chbx = (CheckBox)sender;
ListCollectionView view = _dg_DatasZones.DataContext as ListCollectionView;
if (view == null)
return;
int startindex = Math.Min(_lastSelectedZoneIndex, index);
int endindex = Math.Max(_lastSelectedZoneIndex, index);
for (int i = startindex; i < endindex; i++)
(view.GetItemAt(i) as TaskZoneData).IsChecked = (chbx.IsChecked == true);
}
else
{
_lastSelectedZoneIndex = index;
}
e.Handled = true;
}
For now this works only for selecting the diapason, but not for unselecting it.
To select, you should check the beginning row (checkbox) then press Shift and check the ending row — and the whole diapason will be selected.

Wpf selecteditem not working - Linq

i have one grid binded with some data, coming from database as bellow and one edit button
<DataGrid AutoGenerateColumns="False" Name="SParts_grid" HorizontalAlignment="Center" Margin="32,101,32,0" VerticalAlignment="Top" Height="187" Width="530" >
<DataGrid.Columns>
<DataGridTextColumn Header="Part No" Binding="{Binding Path=SPartID}" />
<DataGridTextColumn Header="Part Code" Width="85" Binding="{Binding Path=SPartCode}" />
<DataGridTextColumn Header="Part Name" Width="160" Binding="{Binding Path=SPartName}" />
<DataGridTextColumn Header="Model" Width="120" Binding="{Binding Path=ModelName}" />
<DataGridTextColumn Header="Location" Binding="{Binding Path=SPartLocation}" />
<DataGridCheckBoxColumn Header="Active" Width="58" Binding="{Binding Path=SPartActive}" />
</DataGrid.Columns>
</DataGrid>
<Button x:Name="btnEdit" Content="Edit" HorizontalAlignment="Left" Margin="105,323,0,0" VerticalAlignment="Top" Width="75" Click="btnEdit_Click"/>
all data fetched from db table called TblSpareParts just one column "ModelName" is from another table called TblBikeModels
so my code is below to fetch data
window loaded function
private void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadParts();
}
private void LoadParts()
{
RST_DBDataContext conn = new RST_DBDataContext();
var AllPArts = (from s in conn.TblSpareParts
join m in conn.TblBikeModels on s.ModelID equals m.ModelID
select new { s.SPartName, s.SPartCode, s.SPartLocation, s.SPartID, m.ModelName }).ToArray();
SParts_grid.ItemsSource = AllPArts;
}
it works well but now if i need the selecteditem it does not work as below
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
TblSparePart SelectedSPData = SParts_grid.SelectedItem as TblSparePart;
if (SelectedSPData == null)
{
MessageBox.Show("You Must Select a Part");
}
else
{
MessageBox.Show("Selected");
}
}
but when i use this LoadParts function then selecteditem works fine but it does not show the data in ModelName column
private void LoadParts()
{
RST_DBDataContext conn = new RST_DBDataContext();
List<TblSparePart> AllPArts = (from s in conn.TblSpareParts
select s).ToList();
SParts_grid.ItemsSource = AllPArts;
}
Basically problem is in LoadParts function
When you do select new { s.SPartName, s.SPartCode, s.SPartLocation, s.SPartID, m.ModelName }, you return an anonymous type, instead of TblSparePart object.
That's why the casting SParts_grid.SelectedItem as TblSparePart; returns null.
I think you have a UI design problem. Working with joined tables in DataGrid will definitely get duplicated data, that's what you may have to avoid. A better solution is to work with two DataGrids or (ComboBox and a DataGrid) one for the models table the other one is for items related to the model.
If you are working with Entity framework, then the task is much easier. You just have to point the binding to the related model table.
Take a look at this case :
public class Item
{
public int Id {get;set;}
public string Name {get;set;}
public Model Model {get;set;}
}
public class Model
{
public int Id {get;set;}
public string Name {get;set;}
}
Here you can do the as you did secondly. By loading only Items table in your DataGrid you can add a column that points to the Model Name property. It's going to be ready to be shown due to lazy loading that EF offers.
<DataGridTextColumn Header="Model" Width="120" Binding="{Binding Path=Model.name}" />

WPF DataGrid: Blank Row Missing

I am creating a WPF window with a DataGrid, and I want to show the blank "new item" row at the bottom of the grid that allows me to add a new item to the grid. For some reason, the blank row is not shown on the grid on my window. Here is the markup I used to create the DataGrid:
<toolkit:DataGrid x:Name="ProjectTasksDataGrid"
DockPanel.Dock="Top"
Style="{DynamicResource {x:Static res:SharedResources.FsBlueGridKey}}"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedProject.Tasks}"
RowHeaderWidth="0"
MouseMove="OnStartDrag"
DragEnter="OnCheckDropTarget"
DragOver="OnCheckDropTarget"
DragLeave="OnCheckDropTarget"
Drop="OnDrop"
InitializingNewItem="ProjectTasksDataGrid_InitializingNewItem">
<toolkit:DataGrid.Columns>
<toolkit:DataGridCheckBoxColumn HeaderTemplate="{DynamicResource {x:Static res:SharedResources.CheckmarkHeaderKey}}" Width="25" Binding="{Binding Completed}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Days" Width="75" Binding="{Binding NumDays}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Due Date" Width="75" Binding="{Binding DueDate, Converter={StaticResource standardDateConverter}}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}" IsReadOnly="false"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
I can't figure out why the blank row isn't showing. I have tried the obvious stuff (IsReadOnly="false", CanUserAddRows="True"), with no luck. Any idea why the blank row is disabled? Thanks for your help.
You must also have to have a default constructor on the type in the collection.
Finally got back to this one. I am not going to change the accepted answer (green checkmark), but here is the cause of the problem:
My View Model wraps domain classes to provide infrastructure needed by WPF. I wrote a CodeProject article on the wrap method I use, which includes a collection class that has two type parameters:
VmCollection<VM, DM>
where DM is a wrapped domain class, and DM is the WPF class that wraps it.
It truns out that, for some weird reason, having the second type parameter in the collection class causes the WPF DataGrid to become uneditable. The fix is to eliminate the second type parameter.
Can't say why this works, only that it does. Hope it helps somebody else down the road.
Vincent Sibal posted an article describing what is required for adding new rows to a DataGrid. There are quite a few possibilities, and most of this depends on the type of collection you're using for SelectedProject.Tasks.
I would recommend making sure that "Tasks" is not a read only collection, and that it supports one of the required interfaces (mentioned in the previous link) to allow new items to be added correctly with DataGrid.
In my opinion this is a bug in the DataGrid. Mike Blandford's link helped me to finally realize what the problem is: The DataGrid does not recognize the type of the rows until it has a real object bound. The edit row does not appear b/c the data grid doesn't know the column types. You would think that binding a strongly typed collection would work, but it does not.
To expand upon Mike Blandford's answer, you must first assign the empty collection and then add and remove a row. For example,
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// data binding
dataGridUsers.ItemsSource = GetMembershipUsers();
EntRefUserDataSet.EntRefUserDataTable dt = (EntRefUserDataSet.EntRefUserDataTable)dataGridUsers.ItemsSource;
// hack to force edit row to appear for empty collections
if (dt.Rows.Count == 0)
{
dt.AddEntRefUserRow("", "", false, false);
dt.Rows[0].Delete();
}
}
Add an empty item to your ItemsSource and then remove it. You may have to set CanUserAddRows back to true after doing this. I read this solution here: (Posts by Jarrey and Rick Roen)
I had this problem when I set the ItemsSource to a DataTable's DefaultView and the view was empty. The columns were defined though so it should have been able to get them. Heh.
This happned to me , i forgot to new up the instance and it was nightmare for me . once i created an instance of the collection in onviewloaded it was solved.
`observablecollection<T> _newvariable = new observablecollection<T>();`
this solved my problem. hope it may help others
For me the best way to implement editable asynchronous DataGrid looks like that:
View Model:
public class UserTextMainViewModel : ViewModelBase
{
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
this._isBusy = value;
OnPropertyChanged();
}
}
private bool _isSearchActive;
private bool _isLoading;
private string _searchInput;
public string SearchInput
{
get { return _searchInput; }
set
{
_searchInput = value;
OnPropertyChanged();
_isSearchActive = !string.IsNullOrEmpty(value);
ApplySearch();
}
}
private ListCollectionView _translationsView;
public ListCollectionView TranslationsView
{
get
{
if (_translationsView == null)
{
OnRefreshRequired();
}
return _translationsView;
}
set
{
_translationsView = value;
OnPropertyChanged();
}
}
private void ApplySearch()
{
var view = TranslationsView;
if (view == null) return;
if (!_isSearchActive)
{
view.Filter = null;
}
else if (view.Filter == null)
{
view.Filter = FilterUserText;
}
else
{
view.Refresh();
}
}
private bool FilterUserText(object o)
{
if (!_isSearchActive) return true;
var item = (UserTextViewModel)o;
return item.Key.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase) ||
item.Value.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase);
}
private ICommand _clearSearchCommand;
public ICommand ClearSearchCommand
{
get
{
return _clearSearchCommand ??
(_clearSearchCommand =
new DelegateCommand((param) =>
{
this.SearchInput = string.Empty;
}, (p) => !string.IsNullOrEmpty(this.SearchInput)));
}
}
private async void OnRefreshRequired()
{
if (_isLoading) return;
_isLoading = true;
IsBusy = true;
try
{
var result = await LoadDefinitions();
TranslationsView = new ListCollectionView(result);
}
catch (Exception ex)
{
//ex.HandleError();//TODO: Needs to create properly error handling
}
_isLoading = false;
IsBusy = false;
}
private async Task<IList> LoadDefinitions()
{
var translatioViewModels = await Task.Run(() => TranslationRepository.Instance.AllTranslationsCache
.Select(model => new UserTextViewModel(model)).ToList());
return translatioViewModels;
}
}
XAML:
<UserControl x:Class="UCM.WFDesigner.Views.UserTextMainView"
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:model="clr-namespace:Cellebrite.Diagnostics.Model.Entities;assembly=Cellebrite.Diagnostics.Model"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:converters1="clr-namespace:UCM.Infra.Converters;assembly=UCM.Infra"
xmlns:core="clr-namespace:UCM.WFDesigner.Core"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<DockPanel>
<StackPanel Orientation="Horizontal"
DockPanel.Dock="Top"
HorizontalAlignment="Left">
<DockPanel>
<TextBlock Text="Search:"
DockPanel.Dock="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Margin="0,0,5,0" />
<Button Style="{StaticResource StyleButtonDeleteCommon}"
Height="20"
Width="20"
DockPanel.Dock="Right"
ToolTip="Clear Filter"
Command="{Binding ClearSearchCommand}" />
<TextBox Text="{Binding SearchInput, UpdateSourceTrigger=PropertyChanged}"
Width="500"
VerticalContentAlignment="Center"
Margin="0,0,2,0"
FontSize="13" />
</DockPanel>
</StackPanel>
<Grid>
<DataGrid ItemsSource="{Binding Path=TranslationsView}"
AutoGenerateColumns="False"
SelectionMode="Single"
CanUserAddRows="True">
<DataGrid.Columns>
<!-- your columns definition is here-->
</DataGrid.Columns>
</DataGrid>
<!-- your "busy indicator", that shows to user a message instead of stuck data grid-->
<Border Visibility="{Binding IsBusy,Converter={converters1:BooleanToSomethingConverter TrueValue='Visible', FalseValue='Collapsed'}}"
Background="#50000000">
<TextBlock Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="Loading. . ."
FontSize="16" />
</Border>
</Grid>
</DockPanel>
This pattern allows to work with data grid in a quite simple way and code is very simple either.
Do not forget to create default constructor for class that represents your data source.

Resources