WPF: Get column name from selected row - c#

I am trying to get the Row's particular column from selected item in wpf from DataGrid.
Name of DataGrid is Datagrid_Newsale.
I am getting alert of whole row when it is selected, So i tried mapping its column.
Say if row is-
{ ID = 3, CustomerName = xyz, SaleDate = 05.08.2013 00:00:00, TotalAmount = 10 }
Then it's column CustomerName=xyz is to be shown in textbox.
Getting row-
var copyitem = Datagrid_NewSale.SelectedItem;
if (copyitem == null)
{
MessageBox.Show("Please select values from list");
}
if (copyitem != null)
{
MessageBox.Show(copyitem.ToString());
}
For getting customerName into text box i tried creating a new instance of model-
public class CustomerDetailes
{
public string CustomerName { get; set; }
}
And values from database from Customer Table-
public void viewcustomername()
{
List<CustomerDetailes> ilist = null;
ilist = (from order in db.Customer
select new CustomerDetailes
{
CustomerName= order.CustomerName
}).ToList();
txtCustumer.Text = ilist.ToString();
}
So giving it one more try-
CustomerDetailes copyitem = (CustomerDetailes)Datagrid_NewSale.SelectedItem;
if (copyitem == null)
{
MessageBox.Show("Please select values from list");
}
if (copyitem != null)
{
MessageBox.Show(copyitem.ToString());
}
txtCustomer.text=copyitem.CustomerName; //CustomerName into a textbox
But it is referencing null in copyitem.
How can I get particular column from the whole row.

You will have to bind the ItemsSource of DataGrid to CustomerDetails collection in order to get CustomDetails in SelectedItem.
Create property in your viewmodel (if using MVVM) or in code behind like
List<CustomerDetails> customerDetails = new List<CustomerDetails>();
List<CustomerDetails> MyCollection
{
get
{
return myList;
}
set
{
myList = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyCollection"));
}
}
and in xaml just do.
<DataGrid ItemsSource="{Binding MyCollection}"/>
OR if you are directly filling the Items in the datagrid add instances of CustomerDetails like
dataGrid.Items.Add(new CustomerDetails(){Name = "abc"}, xyz propertis)
Thanks

If you can access the grid from your selection event then following should give your the column
((DataGrid)sender).CurrentCell.Column.Header
and use some mapping for the column name to the property of the object your want to show

I came up with this easy solution.
Mapped datatype of copyitem that was Anonymous in my case. In this case using Dynamic datatype solved my problem.
Due to my data was coming dynamically and then i was trying to map out particular column, so it was not really possible to do it statically because there is not data then.
Using Dynamic Datatype-
dynamic copyitem = dataGrid1.SelectedItem;
Accessing property-
int localId = copyitem.ID;
furthermore for customerName,TotalAmount i did the same.
Linq Query changes-
var query= (from order in db.Customer
where order.ID=localId
select order).ToList();
DataGrid_OpenSale.ItemsSource=query // returning data to another datagrid.

in VB ⠀⠀⠀
Private Sub SCUSTDataGrid_GotFocus(sender As Object, e As RoutedEventArgs) Handles SCUSTDataGrid.GotFocus
Dim og As DataGridCell = e.OriginalSource
Dim ccontent As TextBlock = og.Content
Dim dg As DataGrid
dg = e.Source
Dim selcol As String = dg.CurrentCell.Column.Header.ToString
MessageBox.Show(selcol.ToString + ccontent.Text + " got focus event")
End Sub

Related

Editing textbox inside datagridview

My project is c# windows form with Entity Framework , and I have DataGridView with a TextColumn. I want to edit the last column TextColumn Cells[3].
To get all values from database to GridView is not problem. I get them.
And I can put a new value but as soon as I click ontherplace then it changes to the old value. Textbox is not keeping the new value that I want to edit.
ReadOnly is false for this cells in column 3 becouse I can write but changes back the new value to the old values.
What kind of events I'am missing and how shall I do to fix this problem? By some how it seems like the hole Gridview is locked. Please Help.
I tried even:
foreach (DataGridViewRow row in dgvOrder.Rows)
{
row.Cells[3].ReadOnly = false;
}
Here below is my code.
private void Treatments_Load(object sender, EventArgs e)
{
try
{
using (MyHealthEntities db = new MyHealthEntities())
{
var orderd = db.Order.Where(x => x.Ordernr == OrNr).FirstOrDefault();
if(orderd != null)
{
var myOrder = (from u in db.....
join d in ...
join m in ...
where u.....
select new
{
OrderId = m.MedId,
Name = m. Name,
Quality = m.Quality,
Description = d.Description
}
).ToList();
if (myOrder != null)
{
dgvOrder.DataSource = myOrder ;
}
}
foreach (DataGridViewRow row in dgvOrder.Rows)
{
row.Cells[3].ReadOnly = false;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
If I'm not mistaken, a normal System.Collections.Generic.List will not support editing when directly bound to because it does not implement IBindingList (or the necessary ListChanged event) for two-way data binding. You will likely need to wrap the List into a BindingList in order to edit the values. This will not be possible with an anonymous type.
First, create a class to store your List items:
public Class Order {
public int OrderID { get; set; }
public string Name { get; set; }
public byte Quality { get; set; }
public string Description { get; set; }
}
Then in your query,
select new Order
{
OrderId = m.MedId,
Name = m. Name,
Quality = m.Quality,
Description = d.Description
}
Now move it to a BindingList:
if (myOrder != null) {
BindingList<Order> myBindingList = New BindingList<Order>(myOrder);
dgvOrder.DataSource = myBindingList;
}
I would also recommend wrapping the BindingList in a BindingSource, which will prevent you from having to handle rows being added/deleted manually:
if (myOrder != null) {
BindingList<Order> myBindingList = New BindingList<Order>(myOrder);
BindingSource myBindingSource = New BindingSource(myBindingList);
dgvOrder.DataSource = myBindingSource;
}
Apologies if my syntax is a little off, I've been mostly using vb as of late.
EDIT: I missed that BindingList does not implement IContainer, so the above code for binding to a BindingSource will not work because the single-parameter constructor for BindingSource specifically takes an IContainer. If you still want to use a BindingSource, the third constructor of BindingSource should be used instead, like so:
BindingSource myBindingSource = New BindingSource(myBindingList, Nothing);
BindingSource does accept binding to an IBindingList, but only using this constructor or by directly setting the .DataSource property after using the parameterless constructor.
Leaving the erroneous code above so others who read OP's comment will understand what was being referenced.
You can't edit DataGrid itens when you are using "dgvOrder.DataSource = myOrder".
My suggestion is you do and "for each" in your myOrder list and add row by Row into Grid. After, you can do an other "for each", recovery the datas from DataGrid and Save the itens in your db.Order.

wpf how to edit datagrid row

I'm using datagrid as the temporary list that until I decide to insert in the database all the data remain in datagrid. I'm adding functions like adding new records, deleting or editing. but I can not change selectedrow
my code is
gridlist.Items.Add(new { num_ins = num_ins.Text, dat_ins = DateTime.Now.ToShortDateString()} --> and many other value
and for delete
var selectedItem = gridlist.SelectedItem;
if (selectedItem != null)
{
gridlist.Items.Remove(selectedItem);
}
i want to make something like this
gridlist.Columns[0].gridlist.Items[1]= "my value";
I wanted to know if it is possible to do this directly from the datagrid as a removal or creation of new records
There are two possibilities.
If you always just add an anonymous type to the Items collection, then it is not possible no update the individual column values. You can however, update the entire row
gridlist.Items[0] = new { num_ins = 1, dat_ins = DateTime.Now};
If you could create a class/structure with public properties for the items being added, then you can update the individual properties of the items.
public class temp
{
public int num_ins {get;set;}
public DateTime dat_ins {get;set;}
}
((gridlist.Items[0] as temp)).num_ins = 3;

How to update image in DataGridView's column with type Text

I have a DataGridView that is being populated by a List. I will send an email and if it is OK I put status = "S" as follows:
var query = (from send in SendMsg
where send.MessageSentId == str
select send)
.Update(st => { st.EmailST = EmailSt; st.SMSST = "N"; });
gvSent.DataSource = null;
gvSent.DataSource = SendMsg;
Everything is working, only now I need to put 2 images containing Ok or Not Ok status. Using the code below, instead of my grid displaying image, it is showing a System.Drawing.Bitmap text. I suspect it is because the column is with type Text and not Image. But this DataGridView is dynamic and I cannot define the types of columns.
private void gvSent_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (gvSent.Columns[e.ColumnIndex].Name == "EmailST")
{
if (gvSent.Rows[e.RowIndex].Cells[6].Value == "S")
{
e.Value = (System.Drawing.Image)Properties.Resources.IMAGE8;
}
else
{
e.Value = (System.Drawing.Image)Properties.Resources.IMAGE9;
}
}
}
When you say this DataGridView is dynamic, it means that its columns are constructed from the data binding. So change the type in your data binding for that column and replace its data with the images before you bind the DataGridView. In such a case, you can use another class or simply an anonymous class:
var sent = SendMsg.Select(s => new {
Id = s.Id,
Name = s.Name,
EmailST = (s.EmailST == "S"? (System.Drawing.Image)Properties.Resources.IMAGE8
: (System.Drawing.Image)Properties.Resources.IMAGE9)
}).ToList();
gvSent.DataSource = null;
gvSent.DataSource = sent;
The fields Id and Name are just example, because I don't know what fields your SendMsg class has. The idea is that you can build an anonymous class based on your SendMsg class and transfer some fields while modifying some other fields.
Alternatively, you can define the columns in the grid (so it is no more dynamic).

Show Properties of a Navigation Property in DataGridView (Second Level Properties)

I'm trying to display several properties from a related entity on a DataGridView in a winforms app. It seems pretty ordinary to me but I'm having trouble finding examples. It's an order entry operation. OrderSheet data, the ID and the pickup date for the order, then the line items (OrderSheetItems in the model below) in the grid. The order lineitems have a navigation property, Product, based on the ProductId. I can use a DataGridViewComboBoxColumn with ProductId as ValueMember and another field as DisplayMember. But I want to include more data in other columns, size, color, material, etc.
Here's the code for loading the data
try
{
_context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...
The ProductId is in a separate column just for experimenting, that will be the combobox later.
So is there a way to bind the other columns to the data in Product navigation property of the OrderSheetItem or do I have to handle CellValueChanged on the product id to physically set the data in the other columns? If there's a way to bind the columns then would that be via code in OnLoad or somewhere in the grid view columns designer?
TIA, Mike
You can use either of these options:
Use DataGridViewComboBoxColumn
Add corresponding properties to child entity partial class
Shape the query to include properties of navigation property using Linq
Use CellFormatting event to get value for sub property bounded columns
Show string representation of object by overriding ToString()
Use a custom TypeDescriptor to enable data binding to sub properties.
Option 1 - Use DataGridViewComboBoxColumn
Usage: This approach would be useful specially in cases which you want to keep the control editable.
In this approach you can use DataGridViewComboBoxColumn to show any field of navigationn property. To show multiple field sub properties of navigation property in grid, use multiple DataGridViewComboBoxColumn bound to same navigation property with different DisplayMember
In this approach, additional to your ProductId column, add more DataGridViewComboBoxColumn to the grid and then perform these settings for all additional combo columns:
Set DataPropertyName of them to ProductId
Set the DataSource property of them, to exactly the same data source you used for main ProductId column, for example productBindingSource
Set ValueMember of them to the same value member you set for product id column, it's the key column of your product table.(ProductId)
Set DisplayMember for each of them to a column that you want to show, for example, set one of them to Name. one to Price, one to Size, ... . This way you can show related entity fields.
Set ReadOnly property of them to true. It makes the cell read only.
If you want to make columns readonly Set DisplayStyle property of them to Nothing. It removes dropdown style.
If you want to keep ProductId editable, keep the DisplayStyle of it to DropDownButton. This way when you change the value of ProductId column using combobox, when you leave the row and moved to next row, you will see other cells of row, shows other properties of the selected product. Also since the other combobox columns are read only and have no combobox style, the user can not change the value of them and they act only like a read only text box column that show other properties from related entity.
Option 2 - Add corresponding properties to child entity partial class
Usage: This approach would be useful when you don't need to edit values.
In this approach, You can define properties in child entity partial class return value of corresponding property of parent entity. For example for product name, define this property in order item partial class:
public string ProductName
{
get
{
if (this.Product != null)
return this.Product.Name;
else
return string.Empty;
}
}
Then you can simply include products when selecting order items and bind the grid column to corresponding properties of order item.
Option 3 - Shape the query to include properties of navigation property
Usage: This approach would be useful when you don't need to edit values.
You can shape the query to include properties of navigation property. You can use an anonymous object or a View Mode simply, for example:
var list = db.OrderDetails.Include("Products").Where(x=>x.OrderId==1)
.Select(x=> new OrderDetailVM() {
Id = x.Id,
ProductId = x.ProductId,
ProductName = x.Product.Name,
Price = x.Product.Price
}).ToList();
Option 4 - Use CellFormatting event to get value for sub property bounded columns
Usage: This approach would be useful when you don't need to edit values.
In this approach you can use CellFormatting event of DataGridView. You can simply set e.Value based on column index. For example:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//I Suppose you want to show product name in column at index 3
if(e.RowIndex>=0 && e.ColumnIndex==3)
{
var orderLineItem= (OrderLineItem)(this.dataGridView1.Rows[e.RowIndex]
.DataBoundItem);
if (order!= null && orderLineItem.Product != null)
e.Value = orderLineItem.Product.Name);
}
}
You can use different criteria to handle different columns and show different sub properties.
Also you can make it more dynamic and reusable using reflection. You can extract the value of sub property of navigation property using reflection. To do so you should create column and set DataPropertyName to sub properties like Product.Name then in CellFormatting event, using reflection, get the value for column. Here is a good article by Antonio Bello about this approach:
DataGridView: How to Bind Nested Objects
Option 5 - Show string representation of object by overriding ToString()
Usage: This approach would be useful when you don't need to edit values.
If you want to show only a single column of navigation property, you can simply override ToString() method of navigation property class and return suitable value. This way, when showing a property of that type in grid, you will see a friendly text. For example in partial class of Product, you can write:
public override string ToString()
{
return this.Name;
}
Option 6 - Use a custom TypeDescriptor to enable data binding to sub properties
Usage: This approach would be useful when you don't need to edit values.
In this approach you can create a custom TypeDescriptor that enables you to perform data binding to second-level properties. Here is a good article by Linda Liu about this approach:
How to bind a DataGridView column to a second-level property of a data source
Using CellFormatting and CellParsing to Show and Edit nested properties in DataGridView
Features:
Any level of nesting is supported.
Both editable and readonly are supported
How it works:
Each column which has "." in the DataPropertyName will be considered as nested property.
CellFormatting event will be handled to get the value of the nested property using a recursive function.
CellParsing event will be handled to set the value of the nested property using a recursive function.
Here are the methods:
public object GetPropertyValue(object source, string name)
{
if (name.Contains("."))
{
var nameParts = name.Split(new[] { '.' }, 2);
return GetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1]);
}
else
{
var property = TypeDescriptor.GetProperties(source)[name];
return property.GetValue(source);
}
}
public void SetPropertyValue(object source, string name, object value)
{
if (name.Contains("."))
{
var nameParts = name.Split(new[] { '.' }, 2);
SetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1], value);
}
else
{
var property = TypeDescriptor.GetProperties(source)[name];
property.SetValue(source, value);
}
}
And here are the event handlers:
private void CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
var dg = (DataGridView)sender;
var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
if (propertyName.Contains("."))
{
var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
e.Value = GetPropertyValue(dataObject, propertyName);
}
}
private void CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
var dg = (DataGridView)sender;
var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
if (propertyName.Contains("."))
{
var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
SetPropertyValue(dataObject, propertyName, e.Value);
}
}
And here is the example:
var categories = new List<Category>() {
new Category{ Id= 1, Name = "C1"},
new Category{ Id= 2, Name = "C2"}
};
var products = new List<Product>() {
new Product(){ Id = 1, Name ="P1", Category = categories[0]},
new Product(){ Id = 2, Name ="P2", Category = categories[0]},
new Product(){ Id = 3, Name ="P3", Category = categories[1]},
};
var dg = new DataGridView();
dg.AutoGenerateColumns = false;
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "Id",
DataPropertyName = "Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "Name",
DataPropertyName = "Name"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "CategoryId",
DataPropertyName = "Category.Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
HeaderText = "CategoryName",
DataPropertyName = "Category.Name"
});
dg.Dock = DockStyle.Fill;
dg.DataSource = products;
this.Controls.Add(dg);
dg.CellFormatting += CellFormatting;
dg.CellParsing += CellParsing;

Filter a DataGrid in WPF

I load a lists of objects in a datagrid with this:
dataGrid1.Items.Add(model);
The model become data from a database. It has a Id(int), Name(string) and Text(string)
In my datagrid I show only the Name of the model. How can I filter the datagrid now, when I enter something in a textbox?
I was at this page: http://msdn.microsoft.com/en-us/library/vstudio/ff407126(v=vs.100).aspx but I don't understand the code from there and I can not explain how I should transpose that for my problem.
there are multiple way's to filter Collection
let's suggesting this is your Item Class
public class Model
{
public string Name
{
get;
set;
}
}
and your collection looks like
var ObColl = new ObservableCollection<Model>();
ObColl.Add(new Model() { Name = "John" });
ObColl.Add(new Model() { Name = "Karl" });
ObColl.Add(new Model() { Name = "Max" });
ObColl.Add(new Model() { Name = "Mary" });
Way 1 (Predicate):
public MainWindow()
{
InitializeComponent();
// Collection which will take your ObservableCollection
var _itemSourceList = new CollectionViewSource() { Source = ObColl };
// ICollectionView the View/UI part
ICollectionView Itemlist = _itemSourceList.View;
// your Filter
var yourCostumFilter= new Predicate<object>(item => ((Model)item).Name.Contains("Max"));
//now we add our Filter
Itemlist.Filter = yourCostumFilter;
dataGrid1.ItemsSource = Itemlist;
}
Way 2 (FilterEventHandler):
public MainWindow()
{
InitializeComponent();
// Collection which will take your Filter
var _itemSourceList = new CollectionViewSource() { Source = ObColl };
//now we add our Filter
_itemSourceList.Filter += new FilterEventHandler(yourFilter);
// ICollectionView the View/UI part
ICollectionView Itemlist = _itemSourceList.View;
dataGrid1.ItemsSource = Itemlist;
}
private void yourFilter(object sender, FilterEventArgs e)
{
var obj = e.Item as Model;
if (obj != null)
{
if (obj.Name.Contains("Max"))
e.Accepted = true;
else
e.Accepted = false;
}
}
extended Information to Way 1
if need multiple conditions or some complex Filter you can add a method to your Predicat
// your Filter
var yourComplexFilter= new Predicate<object>(ComplexFilter);
private bool ComplexFilter(object obj)
{
//your logic
}
This is a simple implementation of using the Filter property of ICollectionView. Suppose your XAML contains this:
<TextBox x:Name="SearchTextBox" />
<Button x:Name="SearchButton"
Content="Search"
Click="SearchButton_OnClick"
Grid.Row="1" />
<DataGrid x:Name="MyDataGrid"
Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Lorem ipsum column"
Binding="{Binding}" />
</DataGrid.Columns>
</DataGrid>
Then in the constructor you can get the default view for your data where you can set the filter predicate which will be executed for every item of your collection. The CollectionView won't know when it should update the collection, so you have to call Refresh when the user clicks the search button.
private ICollectionView defaultView;
public MainWindow()
{
InitializeComponent();
string[] items = new string[]
{
"Asdf",
"qwer",
"sdfg",
"wert",
};
this.defaultView = CollectionViewSource.GetDefaultView(items);
this.defaultView.Filter =
w => ((string)w).Contains(SearchTextBox.Text);
MyDataGrid.ItemsSource = this.defaultView;
}
private void SearchButton_OnClick(object sender, RoutedEventArgs e)
{
this.defaultView.Refresh();
}
At this url you can find a more detailed description of CollectionViews:
http://wpftutorial.net/DataViews.html
#WiiMaxx, can't comment as not enough rep. I would be a bit more careful about the direct casts there. They can be slow for one thing and for another, if the same filter was applied to a grid holding different complex type data you would have an InvalidCastException.
// your Filter
var yourCostumFilter= new Predicate<object>(item =>
{
item = item as Model;
return item == null || item.Name.Contains("Max");
});
This will not break you datagrid and will not filter the results if the cast fails. Less impact to your users if you get the code wrong. On top of that the filter will be faster due to the "as" operator not doing any explicit type coercion as the direct cast operation will.
You can use dataview filter in order to filter the datagrid rows.
DataView dv = datatable.DefaultView;
StringBuilder sb = new StringBuilder();
foreach (DataColumn column in dv.Table.Columns)
{
sb.AppendFormat("[{0}] Like '%{1}%' OR ", column.ColumnName, "FilterString");
}
sb.Remove(sb.Length - 3, 3);
dv.RowFilter = sb.ToString();
dgvReports.ItemsSource = dv;
dgvReports.Items.Refresh();
Where the "datatable" is datasource given to your datagrid and using string builder you build the filter query where "Filter String" is the text you want to search in your datagrid and set it to dataview and finally set the dataview as itemsource to your datagrid and refresh it.
take at look at DataBinding --> in your case dont add items to your grid, but set the itemssource
<Datagrid ItemsSource="{Binding MyCollectionOfModels}" />
or
dataGrid1.ItemsSource = this._myCollectionOfModels;
and if you want some kind of filtering,sorting, grouping look at CollectionView
I found a dumb method and know this is an old question but ... Just use the Filter function on items property on the DataGrid object. Like this: (I'm sorry but i learned only VB)
Public Property SearchName As String
Get
Return _SearchName
End Get
Set
_SearchName = Value
DG_drw_overview.Items.Filter = New Predicate(Of Object)(Function(x) x.Name.Contains(Value))
End Set
End Property
This property is changed every time you type something in the textbox. DG_drw_overview is the DataGrid instance. In Predicate the object represents the object you put in the DataGrid.
Then bind the SearchName to textbox
<TextBox x:Name="TB_search"
Text="{Binding SearchName, UpdateSourceTrigger=PropertyChanged}"/>
Set datacontext of the textbox to the main class (usually after InitializeComponent())
TB_search.DataContext = Me
Leaving this here incase like me you couldn't get the other methods above to work properly.
//Example of my Transaction class
//Transaction(ID, Amount, Description)
ListCollectionView collectionView = new ListCollectionView(List<Transaction>);
collectionView.Filter = (e) =>
{
Transaction transaction = e as Transaction;
if (transaction.Amount >= 0) //When this is true it returns all positive transactions
{
return true;
}
else
{
return false;
}
};
dataGrid.ItemsSource = collectionView;
I used this enclosed in if statements to make a filter by combobox for the datagrid.
The combobox has 3 options Credit/Debit/Credit & Debit, depending on the option I chose it would filter on a selection change event
I got this from here: http://dotnetpattern.com/wpf-datagrid-filtering

Categories

Resources