WPF DataGrid Double-Click Exception - wpf

Long-time WinForms programmer relatively new to WPF. I'm databinding a DataGrid in Code-Behind using a SQL Query. I reuse the DataGrid because I'm using Ribbon Tabs and reload different data in the grid dependent on the Tab selected. So binding to a static resource is not possible.
I'm trying to open a new window on a double-click event but get the following exceeption - "Unable to cast object of type 'System.Windows.RoutedEventArgs' to type 'System.Windows.Input.MouseButtonEventArgs'."
This used to be a simple thing in WinForms. My code is as follows:
Try
Dim StrRow As DataRowView = MainDataGrid.SelectedItem
Dim CellValue As String = StrRow.Row(0).ToString()
'MsgBox(CellValue)
e.Handled = True
Dim EventDetails = New EventDetails()
EventDetails.Show()
OpenEventDetailsWindow()
Catch ex As Exception
MsgBox("No Event No.for this Event")
End Try
The messagebox shows the proper string (first cell value of selected row and the new window actually pops up right before the exception is thrown. I've seen plenty of posts that say this method should work but it doesn't. I've been so far unsuccessful in fixing this. Thanks in advance.

Related

Handling Events For Controls Added At Run Time

I am adding controls at run time in a WPF App, these are all different types of control so are passed as a UIElement. I then get the type of control and can set the properties using SetValue. As I have a lot of control types to implement what I am now trying to do is is add an event to each control but can only seem to do this using Addhandler which requires a lot of extra code for each control as shown below. I am looking for a solution to allow me to add the error handler to the UIElement without prior conversion using a Cast, I am not sure this is possible?
Select Case Control.GetType()
Case GetType(ExtendedTextBox)
Dim newControl As ExtendedTextBox = TryCast(Control, ExtendedTextBox)
'Dim newCtl As UIElement = TryCast(Control, ExtendedTextBox)
If newControl IsNot Nothing Then
newControl.SetValue(ExtendedTextBox.HorizontalAlignmentProperty, HorizontalAlignment.Left)
newControl.SetValue(ExtendedControlProperties.HighlightTextOnFocusProperty, True)
newControl.SetValue(ExtendedControlProperties.MandatoryProperty, True)
AddHandler newControl.HasErrors, AddressOf UpdateErrorStatus
End If
I was incorrectly prefixing my validation with "Customer." which as in inherited control was causing the issue, once removed it works as expected.

WPF SelectionChange Event for ComboBox with Binding from database crashes the Program

I've got a big issue trying to modify the ItemSource for a ComboBox. When I try to debug the app by putting a breakpoint on MessageBox.Show(...) in the Sub cboTest_SelectionChanged (see below), I get this error:
An Unhandled exception of type 'System.InvalidCastException' occured in Data.exe
Additional Information:
Unable to cast object of type 'System.Data.DataRowView' to type 'System.Windows.Controls.ComboBoxItem'
How can I fix this?
Here's what I do. I create an ACCESS 2007 Database named SNIGDoFFE.accdb, my solution is named Data, I added the Database through the Add New Source Configuration wizard and I drag and drop the Field "Libelle" from the Table Ethnie to the the ComboBox, so it modifies my files like this:
In MainWindow.xaml.vb
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
Dim SNIGDoFFEDataSet As Data.SNIGDoFFEDataSet = CType(Me.FindResource("SNIGDoFFEDataSet"), Data.SNIGDoFFEDataSet)
'Load data into the table Ethnie. You can modify this code as needed.
Dim SNIGDoFFEDataSetEthnieTableAdapter As Data.SNIGDoFFEDataSetTableAdapters.EthnieTableAdapter = New Data.SNIGDoFFEDataSetTableAdapter.EthnieTableAdapter()
SNIGDoFFEDataSetEthnieTableAdapter.Fill(SNIGDoFFEDataSet.Ethnie)
Dim EthnieViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("EthnieViewSource"), System.Windows.Data.CollectionViewSource)
'EthnieViewSource.View.MoveCurrentToFirst
'I comment this line to leave the combobox empty
End Sub
The code for cboTest_SelectionChanged is:
Private Sub cboTesT_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cboTesT.SelectionChanged
MessageBox.Show(CType(cboTesT.SelectedItem, ComboBoxItem).Content)
End Sub
In XAML:
<ComboBox x:Name="cboTest" width="120" Margin"184,10,183,134" SelectionChanged="cboTest_SelectionChanged" DisplayMemberPath="Libelle" ItemSource="{Binding Source={StaticResource EthnieViewSource}}" />
In my Resources:
<Window.Resources>
<local:SNIGDoFFEDataSet x:Key"SNIGDoFFEDataSet" />
<CollectionViewSource x:Key="EthnieViewSource" Source="{Binding Ethnie, Source={StaticResources SNIGDoFFEDataSet}}" />
</Window.Resources>
The Solution is built correctly, the main Window loaded correctly, but when I select an item in the combobox, the program crashes. I try to remove all binding and create Item with and everything work, but by binding the source from my database, it crashes again.
The program crashes because you allow the exception to propagate out of your code. To stop the program from crashing, place everything in your SelectedChange event handler in a try catch block.
This should become a standard for all 'windows entry points' into your code. Whether it is an event or a command, if you didn't call it from your code, use a try catch block to stop exceptions from propagating out of your code.
Regarding the error, you are binding data to your ComboBox. When you interrogate the SelectedItem, it will be of the type of the data (in your case, of type System.Data.DataRowView), not a ComboBoxItem.
If you need the containing ComboBoxItem of the selected item, use the ItemContainerGenerator.ContainerFromItem() to find it, for example
Private Sub cboTesT_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cboTesT.SelectionChanged
Try
Dim combo As ComboBox = CType(sender, ComboBox)
Dim item As ComboBoxItem = combo.ItemContainerGenerator.ContainerFromItem(combo.SelectedItem)
MessageBox.Show(item.Content)
Catch ex As Exception
' Log or display exception.
End Try
End Sub
If you are just after the data, CType the SelectedItem to your datatype (System.Data.DataRowView) and work with it directly.
I hope this helps.
Sorry Mark, to be so long
and again I'm sorry a lot
on msdn forum, they told me that as the binding source is a Table in a database I must use DataRowView instead of ComboBoxItem,
Dim drv As System.Data.DataRowView = CType(cboTest.SelectedItem, DataRowView)
MessageBox.show(drv("Libelle").ToString())
Don't hesitate to help me another time, please.

How to retrieve a particular cell value from a wpf datagrid without selecting the cell or the respective row?

I m working with WPF DataGrid and I want to retrieve a DataGridCell's value by using the column and the row indexes : all what I could do is this but it didn't work :
myDatGrid.Columns[0].GetCellContent((DataGridRow)myDatGrid.Items[0]);
Could you please show me the way to realize that
Your cells value is going to be contingent on what the given column is bound to. The entire row will be the instance of your model.
Assume we have a collection of Person classes which we are binding to within our DataGrid.
Person p = ((ContentPresenter)myDatGrid.Columns[0].GetCellContent(myDatGrid.Items[0])).Content;
The Content property is going to return the underlying model for the row. If you wanted to obtain a given property you can do so by directly accessing the underyling object which should implement INotifyPropertyChanged, no need to fool with the DataGrid as you would in a WinForms application.
I tried what has been proposed above and it did not work.
I struggled for a whole day trying to bind a ComboBox and select the correct value in a WPF Datagrid when it first loads, so I thought I would share my solution here in hopes that somebody else could benefit. Sorry about the VB, but the concept should work in C# also.
First, if you are trying to get the values populated and selected when the grid first loads, you need to put your code in the correct event handler: LayoutUpdated. In other event handlers, the GetCellContent function returns Nothing (null). You could also put this code in an event handler which handles an event occurring later, such as a Button.Click event, if that meets your requirements.
Second, the code:
For i As Int32 = 0 To DataGrid1.Items.Count - 1
Dim row As DataGridRow
row = DataGrid1.ItemContainerGenerator.ContainerFromItem(DataGrid1.Items(i))
If row IsNot Nothing AndAlso row.Item IsNot Nothing Then
Dim cp As ContentPresenter = DataGrid1.Columns(3).GetCellContent(DataGrid1.Items(i))
If cp IsNot Nothing AndAlso cp.ContentTemplate IsNot Nothing Then
Dim dt As DataTemplate = cp.ContentTemplate
If dt IsNot Nothing Then
Dim cb As ComboBox = dt.FindName("cbVendorNames", cp)
If cb IsNot Nothing Then
cb.ItemsSource = Vendors
cb.DisplayMemberPath = "VendorName"
cb.SelectedValuePath = "AS_ID"
cb.SelectedValue = "" ' set your selected value here
End If
End If
End If
End If
Next
What this code does is (a) loop through all of the rows in the datagrid, (b) get the ContentPresenter for the cell selected (in this case, cell 3 in each row), (c) get the DataTemplate for the ContentPresenter, and (d) finally, get the reference to the ComboBox. This allowed me to bind the ComboBox to an external datasource (a List(Of Vendor) and select its value. This code worked for me.
I do not find this solution particularly elegant or efficient, but it does work. If somebody has a better solution, please post it.
OK I think I got it , I missed some casts that I should put , because the column I'm using is a DataGridComboBoxColumn
I should actually do this :
((ComboBox)(myDatGrid.Columns[0].GetCellContent(
(TestData)myDatGrid.Items[0]))).SelectedValue.ToString());
Now it works.

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 Row add in codebehind

I am from VB.Net WinForms comming. Now I wanted to write a small app in WPF, listing some files in a datagridview. I used WPF's DataGrid, created some Columns. And then failed to add my rows.
Please, can you help me to select the right way to get my filenames, state-text and thumbnails added to the DataGrid Row?
In VB.Net WinForms I can add a row like this:
Datagridview1.Rows.add(Myvalue, "RowStateText", "Hello World", MyDate)
In WPF's DataGrid I can add
DataGrid1.Items.Add(New DataGridRow())
But how to fill my DataGridRow?
Private Sub AddFilesAndFolders(ByVal Base As IO.DirectoryInfo, ByRef dgv As DataGrid)
'For Each di As IO.DirectoryInfo In Base.GetDirectories
' Call AddFilesAndFolders(di, dgv)
'Next
Dim item As DataGridRow
For Each fi As IO.FileInfo In Base.GetFiles
item = New DataGridRow'<-- test 1 (row is added but empty)
Dim di As New MyFileInfo'<-- test 2 (my own class with public members, but how to add as row with declared columns?)
di.FileName = fi.FullName
di.FileDate = fi.LastAccessTime
item.Item = fi.FullName
dgv.Items.Add(di)
Next
End Sub
Hi: you should set an ItemsSource instead of adding items manually. If the columns are set up correctly then it will just 'work'!
dbv.ItemsSource = Base.GetFiles
or
dbv.ItemsSource = CreateMyFileInfos(Base.GetFiles)
If you have any more problems, please post back here.
Edit: on second inspection it looks like you may want to be doing it recursively. In which case your AddFilesAndFolders could instead be CreateFilesAndFolders, which would return a collection of FileInfo/MyFileInfo objects, merged with the collections produced by the child folders recursively; then bind the whole list returned from the first call, to the grid.
Hope that helps!
WPF is a mindset change, you need to get away from the Winforms way of thinking.
Ultimately you need to set the ItemsSource to an IEnumerable, preferably a ObservableCollection.
The quickest way to get started would be to put the ObservableCollection as a public property in your code-behind file:
public ObservableCollection<DirectoryInfo> files { get;set; }
Then in the constructor or a Load event on the Window, populate the collection with your data and then add to the Xaml declaration for your DataGrid:
ItemsSource = "{Binding Path=files}"
EDIT:
I tried this out using the DirectoryInfo class, in my code behind I added:
public ObservableCollection<DirectoryInfo> Dir = new ObservableCollection<DirectoryInfo>();
public Window1()
{
InitializeComponent();
Dir.Add(new DirectoryInfo("c:\\"));
Dir.Add(new DirectoryInfo("c:\\temp\\"));
dataGrid1.ItemsSource = Dir;
}
For some reason this was not working using the Databinding via Xaml, but I did not try very hard to get it to work.

Resources