WPF DataGridComboBoxColumn binding - wpf

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
}
}

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.

How to get the SelectedItem property from a ComboBox column in a DataGrid wpf

recently we started to use WPF at work.
Now I want to create a DataGrid from a list of objects (DataGrid ItemSource) that contains a project role, the employee that should do the job and a list of employees that could also do that job. Lets call this list the "MainList". In that DataGrid is a ComboBox Column that uses another list of objects as ItemSource where you can change the employee for the job. I will call this list the "ChildList". This list is included in the MainList (as allready mentioned) and I bind it by using the correct BindingPath. So far so good. Now I have to set the SelectedItem (to show which Employee is currently selected). From the MainList I can get the employee that should be selected from the ChildList. Obviously I cant do this by Binding. Unfortunatly I cant get the SelectedItem property in the code-behind. Basically I need to go through every row from the DataGrid and get the Item that should be selected in the ComboBox. Then I would go through the ComboBox Items until I find the matching Item and set this as SelectedItem. But I cant find a way to do this.
I tried using a DataGridComboBoxColumn aswell but it has only the SelectedItemBinding property and since you cant compare while binding this should not work.
I also tried to get every cell in the code-behind, that is a ComboBox but without success so far.
<DataGrid x:Name="DgvProjectTeam" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" Margin="0" RowHeight="40" CanUserAddRows="False" BorderThickness="1" VerticalScrollBarVisibility="Auto" HorizontalGridLinesBrush="#FFA2B5CD" VerticalGridLinesBrush="#FFA2B5CD" ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Resource" Width="200" x:Name="DgtProjectCoreTeam">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="CbxResource" ItemsSource="{Binding Path=ListOfPossibleResources}" DisplayMemberPath="ResourceOfQMatrix.Fullname"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The DataGrid shows everything I need. I just dont know how I can set the SelectedItem for each of the generated ComboBoxCells in the code-behind.
Anyone an idea?
Here's a quick example of what you can do. First, define your view models that will be bound to the DataGrid. Ideally, these view models would raise PropertyChanged or CollectionChanged when their properties are changed, but for this simple example this is not needed.
public class ViewModel
{
public List<ProjectRoleViewModel> ProjectRoles { get; set; }
}
public class ProjectRoleViewModel
{
public string Role { get; set; }
public string Employee { get; set; }
public List<string> OtherEmployees { get; set; }
public string SelectedOtherEmployee { get; set; }
}
I've hard-coded some dummy values to have data in the view models:
var viewModel = new ViewModel
{
ProjectRoles = new List<ProjectRoleViewModel>
{
new ProjectRoleViewModel
{
Role = "Designer",
Employee = "John Smith",
OtherEmployees = new List<string> {"Monica Thompson", "Robert Gavin"}
},
new ProjectRoleViewModel
{
Role = "Developer",
Employee = "Tom Barr",
OtherEmployees = new List<string> {"Jason Ross", "James Moore"}
}
}
};
This view model then needs to be assigned to the DataContext of your Window or UserControl that contains the DataGrid. Here's the XAML for the DataGrid:
<DataGrid
ItemsSource="{Binding ProjectRoles}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Role}" />
<DataGridTextColumn Binding="{Binding Employee}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding OtherEmployees}"
SelectedItem="{Binding SelectedOtherEmployee, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
After the user picks the "other" employee that can do the job, the view model's SelectedOtherEmployee will have the chosen value. You don't need any code-behind in this case, everything's contained in the view models.

Set current date based on selection form ComboBox using WPF and MVVM

I am trying to figure out a way to get the current date and set that value to "Completed On" only when Status is set to "Inactive if Status is set to "Active" then i want Completed On to be empty. I was think this could be accomplished by using a trigger event. I am also writing this app in MVVM format.
<telerik:RadGridView x:Name="dgRad" Grid.Column="0" Grid.Row="1">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn Header="Completed On"
Width="Auto"
DataMemberBinding="{Binding EndDate, Mode=OneWay}" />
<telerik:GridViewDataColumn Header="Status"
DataMemberBinding="{Binding Status, Mode=TwoWay}"
Width="Auto" IsReadOnly="False"
IsEnabled="True" IsVisible="True">
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<ComboBox Text="{Binding Path=Status, Mode=TwoWay}">
<ComboBoxItem>Active</ComboBoxItem>
<ComboBoxItem>Inactive</ComboBoxItem>
</ComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:RadGridView.Columns>
This is logic that you want at your view model level, not in the XAML.
Depending on what sort of framework you might be using, there's different ways of detecting value changes, but it would look something like this:
this.NotifyPropertyChanged += (o, e) => {
if (e.PropertyName == "Status")
{
if (this.Status == "Active") EndDate = null; //nullable DateTime?
else if (this.Status == "Inactive") EndDate = DateTime.Now;
}
};
In MVVM, your view only does things related to displaying things to the user. Logic belongs in the view model. Note that this code relates to the line item object being displayed in the grid, not your outer view model.
Hopefully this helps you get started.
This should help....
public class YourViewModel
{
public string Status{get;
set{
...
RaisePropertyChange("EndDate");
}
}
public string EndDate{
get{
return Status=="Active" ? "Completed On" + DateTime.Now : "";
}
}
}

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}" />

Bind DataGridTextColumn with calculation in MVVM

I am new in Silverlight MVVM, I am creating a project where I am binding a data in to DataGrid.
Here is my database structure:
tblAuthorizationVarification
(AuthorizationVarificationid, AuthorizationRequestid, number)
tblAuthorizationRequest (AuthorizationRequestid, name)
tblAuthorizationVarificationDetails (DetailId, AuthorizationRequestid, Amount)
I want to show Total of Amount in gridview for all authorization.
Below is my code, in ViewModel class, I'm getting tblAuthorizationRequest from tblAuthorizationVarification:
PagedCollectionView _AuthorizationVarificationList;
public PagedCollectionView AuthorizationVarificationList
{
get { return _AuthorizationVarificationList; }
set
{
_AuthorizationVarificationList = value;
OnPropertyChanged("AuthorizationVarificationList"); }
}
private void GetVarifications()
{
IsBusy = true;
LoadOperation<AuthorizationVerification> loadOp =
objContext.Load(objContext.GetCreditNotesQuery());
loadOp.Completed += (sender, e) =>
{
IEnumerable<AuthorizationVerification> op =
((LoadOperation<AuthorizationVerification>)sender).Entities;
PagedCollectionView view = new PagedCollectionView(op);
this.AuthorizationVarificationList = view;
cnt = cnt - 1;
if (cnt <= 0)
IsBusy = false;
};
}
AuthorizationVarificationList is binding in Gridview as like
<sdk:DataGrid x:Name="grdCreditNotes"
ItemsSource="{Binding Path=AuthorizationVarificationList}"
SelectedItem="{Binding Path=SelectedCreditNote, Mode=TwoWay}"
AutoGenerateColumns="False" IsReadOnly="True" Grid.Row="2"
VerticalAlignment="Stretch" Margin="0,0,0,0">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Credit No"
Binding="{Binding Path=AuthorizationVerificationId}" Width="200"/>
<sdk:DataGridTextColumn Header="Amount"
Binding="{Binding Path=AuthorizationRequest.Amount}" MinWidth="100"
Width="*"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
What can I do for display sum of amount of particular Authorization in this field of grid?
As I've already suggested, you can create a view model class for the collection item and populate it as it should be.
public class VerificationViewModel
{
public int AuthorizationVerificationId { get; set; }
public double Amount { get; set; }
}
Then use the LINQ-query which groups the collection and returns summarized items:
loadOp.Completed += (sender, e) =>
{
IEnumerable<AuthorizationVerification> op =
((LoadOperation<AuthorizationVerification>)sender).Entities;
var models = op.GroupBy(item => item.AuthorizationVerificationId)
.Select(g => new VerificationViewModel
{
AuthorizationVerificationId = g.Key,
Amount = g.Sum(gi => gi.Amount)
})
.ToList();
PagedCollectionView view = new PagedCollectionView(models);
// Everything else is the same
}
//Also change the type of the property which is bound to SelectedItem
public VerificationViewModel SelectedCreditNote { get; set; }
And change the binding path of the second column:
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Credit No"
Binding="{Binding Path=AuthorizationVerificationId}" Width="200"/>
<sdk:DataGridTextColumn Header="Amount"
Binding="{Binding Path=Amount}" MinWidth="100" Width="*"/>
</sdk:DataGrid.Columns>
This code should calculate the sum of the Amount for each Id. If you want some other aggregation, you can change the linq query.
If I understand correctly you are after a Summary Row? If so, please check out the following link http://leeontech.wordpress.com/2010/02/01/summary-row-in-datagrid/. You will may need to shape your Model objects behind ViewModels a little better (It is rarely good practice to show Models (DTOs in this case) directly to the View).

Resources