Validating new rows in Silverlight DataGrid - silverlight

We're using RIA Services / Silverlight 4, and I'm binding a datagrid to something like Context.Foo.
I can see validation errors appearing in the datagrid's validation summary when users try to enter empty strings into mandatory fields and so forth, life is good.
However when I add a new item by calling something like Context.Foo.Add(new Foo) in the viewModel, the new row appears in the datagrid, but is never validated unless the user clicks on a cell.
Is there a way to ask the DataGrid is validate all items?

Rather than asking the DataGrid to validate the row for you, you will need to validate the object itself that the new row is bound to. You can use the Validator class to do this for you. For example, say your object is assigned to a variable named newRowObject, you can do the following:
List<ValidationResult> validationResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(newRowObject, null, null);
bool isValid = Validator.TryValidateObject(newRowObject, validationContext, validationResults, true);
This should achieve what you are after (I emphasis should, simply because I didn't check it myself in an example before I wrote this).
Hope this helps...
Chris

Related

Add columns and rows to wpf listview dynamically

i want to add new columns and rows to a C# WPF ListView (GridView) at runtime. As far as i know you can only add rows to a gridview by using anonymous objects or classes with a static set of members to which the columns are bound. Is there a way to do this at runtime that the user is able to add a new column, bind this column to something and add new data?
thx
ooorndtski
Yes, you can do this in code an runtime. You need the GridView as a variable (give it a name in XAML to auto-generate that variable in Visual Studio). The GridView has a Columns property that you can handle like any other collection, you can add and remove for example.
This is the example from MSDN (The gridview name "myGridView"):
GridViewColumn gvc3 = new GridViewColumn();
gvc3.DisplayMemberBinding = new Binding("EmployeeNumber");
gvc3.Header = "Employee No.";
gvc3.Width = 100;
myGridView.Columns.Add(gvc3);
Generally speaking, anything you can do in XAML, you can do in code.
So, what i was looking for is described in this thread.
He creates a new Dictionary-class, which implements the INotifyPropertyChanged interface.
When adding data to the dictionary an event is triggered.
At the place in your code where you want to add a new row, you just put the data into an object of this Dictionary class and add the Dictionary to an ObservableCollection which is bound to the DataGrid.

How can I set a BindingSource current record to null?

I have a capture form for a Works Order, and it has a CustomerBindingSource and a WorksOrderBindingSource control. Most edit fields are bound to the WorksOrderBindingSource, with a ComboBox whose list is bound to the CustomerBindingSource, and its SelectedValue is bound to the CustomerId field in the WorksOrderBindingSource. This is all very routine and standard, no funnies here.
Then, I also have some textbox fields in which I use to show properties of the currently selected customer, for the currently edited works order. I have bound these fields to the CustomerBindingSource as well. When a customer is selected, these fields show properties of that customer as expected.
My problem is when I want to use the form to capture a new works order. I instantiate a new WorksOrder object, with CustomerId == null and bind it to the WorksOrderBindingSource. I have no object in the CustomerBindingSource with an Id == null, so, as expected, the dropdown combobox is blank, but, the CustomerBindingSource.Current property points to the first Customer object in that datasource. The customer linked display fields show values for that customer, while no customer has been selected yet.
The only workaround for this that is apparent to me seems clumsy. In it, I have two Customer typed binding sources, one for a selected customer, and to populate the customer display fields, and another merely for populating the customer dropdown. Then, I have to handle a selection event, and only if a customer is selected, then find that customer in the binding source for the display fields, and if none selected, set the datasource for the display fields to null. This feels terribly clumsy. Is there any other way of achieving what I want?
I found this topic with exactly my problem but without satisfying answer. I know its an old topic but alah..
I ended up with a working solution: I added a [PositionChanged] event to my bindingsource (would be your CustomerBindingSource).
private void CustomerBindingSource_PositionChanged(object sender, EventArgs e)
{
if(<yourCombobox>.SelectedIndex==-1)
{
CustomerBindingSource.SuspendBinding();
}
else
{
CustomerBindingSource.ResumeBinding();
}
}
What I use to "clear" a BindingSource is to simply set its DataSource like this:
CustomerBindingSource.DataSource = typeof(Customer);
Hope this helps.
EDIT:
For clarity, when you set the BindingSource.DataSource property as described, there's nothing preventing you to reassign the original data source at a later point:
//Retrieve customers from database
List<Customer> Customers = WhatEverCallToDB();
CustomerBindingSource.DataSource = Customers;
...
//Later we need to blank the Customer fields on the Windows Form
CustomerBindingSource.DataSource = typeof(Customer);
...
//Then again at a later point we can restore the BindingSource:
CustomerBindingSource.DataSource = Customers;
...

After adding a new row why aren't my bound controls updating?

I'm using wpf drag and drop databinding to datasets. I generate a new datatable row using the AddNew method of the BindingListCollectionView. I set values on the new row and call CommitNew on the BindingListCollectionView. I expect to see my assigned values in the bound controls but they are all blank. If I save the changes to the database the controls are then updated, but I want the user to see my assigned values before calling UpdateAll on the TableAdapterManager.
Background:
I've created a strongly typed dataset in a project separate from my wpf application. My wpf app references the dataset application. I added an object datasource in the wpf app pointing to the typed dataset. I dragged fields/controls from the datasources window to the wpf designer window. The generated xaml includes a Window.Resources section with a CollectionViewSource accurately bound to my dataset and datatable. This CollectionViewSource is the DataContext for the controls that I dragged to the design surface. All of the controls use TwoWay databinding.
When the window loads I grab a reference to the xaml's CollectionViewSource (using FindResource). I then grab a reference to the view property of the CollectionViewSource and cast it to a BindingListCollectionView. Now I use the AddNew method of the BindingListCollectionView to generate a new row (which AddNew returns as an object). I cast the object to a DataRowView and access it's Row property. I then cast the row to a strongly typed datatable row (generated by the DataSet designer). Now I assign values to some of the datatable row columns. I call CommitNew on the BindingListCollectionView. Finally I call MoveCurrentToFirst on the CollectionViewSource.
Problem:
Using a watch expression I can see the data is in the SourceCollection of both the CollectionView and the BindingListCollectionView. Can anyone explain why the bound controls do not show the data unless I save the changes to the database?
Code (generated XAML not shown):
Private WithEvents _cvsScanData As System.Windows.Data.CollectionViewSource
Private WithEvents _blcvScanData As System.Windows.Data.BindingListCollectionView
_cvsScanData = CType(Me.FindResource("Dt_tblScanDataViewSource"), System.Windows.Data.CollectionViewSource)
_blcvScanData = CType(_cvsScanData.View, BindingListCollectionView)
Dim newRow As LabDataSet.dt_tblScanDataRow = CType(CType(_blcvScanData.AddNew, System.Data.DataRowView).Row, LabDataSet.dt_tblScanDataRow)
newRow.SampleID = "testSampleID"
newRow.MachineID = "testMachineID"
_blcvScanData.CommitNew()
_cvsScanData.View.MoveCurrentToFirst()
The simple fix is to call the Refresh method of the BindingListCollectionView after calling CommitNew.
_blcvScanData.Refresh()
I stumbled across this answer to my own question via intellisense. If anyone can explain why refresh is necessary I'd appreciate it. I expected the INotifyPropertyChange interface to update the bound controls obviating the need to call refresh.

WPF DataGrid sort by ComboBox field

I have WPF datagrid with combox column (ID is real value, Desc is displayed value) and when I click on header of that column automatically sorts by real value (ID). I want to sort by displayed value.
My WPF datagrid has 4 columns: IdPerson, DescSchool, IdSchool and School. Column "School" is comboBox with this values:ItemSource = schoolTable.DefaultView, SelectedValueBinding = new Binding("IdSchool"), SelectedValuePath="IDSchool", DisplayMemberPath = "DescSchool"
schoolTable is a table with 2 columns - IDSchool and DescSchool. That table is used just as datasource for combobox.
I tried the solution when I have set SortMemberPath = "DescSchool" and initially, this works - when I click on the header of the combobox column sorting is done by displayed value (because it read value of the other column) and not by real value. But, if I change the value of the combobox, value of the column "DescSchool" is still the same so after that sorting doesn't work anymore properly.
Any idea?
Setting SortMemberPath="Desc" (or what your property is called) on the DataGridComboBoxColumn should do the trick.
fall into similar problem recently.
try something like :
SortMemberPath="School.DescSchool"
Hope it will help you or someone else!
I have also had this problem and had to implement IComparable on the type that is being sorted. So in your case I think it is the School type. Inside IComparable, return this:
return this.Desc.CompareTo((obj as School).Desc);
This is the only way I was able to get this to work, and judging by the lack of responses, not many people know a better way...sorry.
Also, this will only work if you have access to the types. If this is a datatable or something like that (as opposed to Entity Framework for example) this solution will not work.
I had similar problem to solve with WinForm and its DataGridView displaying relational data in a combobox column in the grid. Wanted to sort the column but the sorting would sort by the ValueMember (an int ID field) and not the DisplayMember (text field being displayed).
I was using a typed dataset and put its typed table into a DataView and use that DataView as the Datasource for a BindingSource which was then the Datasource for the grid. :-)
After reading several posts on the web, the solution that worked for me was the following:
In the code-behind of the Typed dataset, I added some custom properties to the Typed Row of my main typed table.
Example:
public string ProvinceAtRow
{
get
{
string result = string.Empty;
if (!this.CustomerInfoRow.IsProvinceDescriptionNull())
{
result = this.CustomerInfoRow.ProvinceDescription;
}
return result;
}
}
Next in the code-behind of the WinForm that has an instance of my typed Dataset, I added a DataColumn to the typed table.
Example (code in the Load event of the winForm):
this.DSGrowth.LocalGrowthFactor.Columns.Add(
new DataColumn("Province", typeof(string)));
Next when have data in the dataset, must fill in the column(s) that were added.
Example:
//if we have data then have to fill in the colums we added to the TDS
if (this.DSGrowth.LocalGrowthFactor.Rows.Count > 0)
{
//fill columns so that can display on datagrid as columns that can be sorted
// ref http://www.daniweb.com/forums/thread283915.html
Array.ForEach<dsLocalGrowthFactor.LocalGrowthFactorRow>( this.DSGrowth.LocalGrowthFactor.Rows.OfType<dsLocalGrowthFactor.LocalGrowthFactorRow>().ToArray(),
row =>
{
row["Province"] = row.ProvinceAtRow;
}
);
//accept changes on TDS so not to be prompted to save changes
this.DSGrowth.AcceptChanges();
}
//set default sort
DataView dvFactors = new DataView(this.DSGrowth.LocalGrowthFactor);
dvFactors.Sort = "GrowthFactor DESC";
this.BSLocalGrowth.DataSource = dvFactors;
this.DgvGrowth.DataSource = this.BSLocalGrowth;
if (this.DgvGrowth.Rows.Count > 0)
{
//select first row
this.BSLocalGrowth.MoveFirst();
this.DgvGrowth.Rows[0].Selected = true;
}
No more ComboBox columns and sorting works!
Hope this helps all out there who are in similar situation.

How to do get this CRUD code to work on WPF DataGrid?

I'm writing CRUD code for the WPF Datagrid.
In the TheDataGrid_CellEditEnding method below:
how do I get the original text before the user made the change?
I need the original text to be able to change the customer and save it back to the database with _db.SubmitChanges()
Here's the full solution with database if anyone wants to experiment with this:
http://www.tanguay.info/web/download/testDataGrid566northwindDatagrid.zip
XAML:
<toolkit:DataGrid x:Name="TheDataGrid"
AutoGenerateColumns="True"
CellEditEnding="TheDataGrid_CellEditEnding"/>
code-behind:
private void TheDataGrid_CellEditEnding(object sender, Microsoft.Windows.Controls.DataGridCellEditEndingEventArgs e)
{
//get the original text
Customer customer = e.Row.Item as Customer;
string customerID = customer.CustomerID;
int displayIndex = (int)e.Column.DisplayIndex; // e.g. equals 4 when user edits the 5th column
//HOW TO I GET THE ORIGINAL TEXT? THERE IS NO FIELDS METHOD IN THE LINQ-TO-SQL CLASSES
string originalText = customer.Fields[displayIndex].value.ToString();
//get the changed text
TextBox changedTextBox = e.EditingElement as TextBox;
string changedText = changedTextBox.Text;
//inform user
Message.Text = String.Format("cell was changed from {0} to {1}", originalText, changedText);
//I NEED TO CHANGE THE CUSTOMER WITH THE ABOVE TEXT
//BEFORE I SAVE IT BACK HERE
_db.SubmitChanges();
}
Why do you need the original text? Is it to display some informational message?
In your case, you seem to be binding the datagrid to your LinqToSQL objects. This means that the Customer object the row is bound to is already updated and all you need to do is call SubmitChanges().
I found the samples in this blog very helpful
http://blogsprajeesh.blogspot.com/2009/03/blog-post.html
It explains sorting using the collection and grid events.

Resources