Apply and validate a bound DataGridViewComboBoxCell directly upon selection change - winforms

I have a windows forms DataGridView that contains some DataGridViewComboBoxCells that are bound to a source collection using DataSource, DisplayMember and ValueMember properties. Currently the the combobox cell commits the changes (i.e. DataGridView.CellValueChanged is raised) only after I click on another cell and the combobox cell loses focus.
How would I ideally commit the change directly after a new value was selected in the combobox.

This behaviour is written into the implementation of the DataGridViewComboBoxEditingControl. Thankfully, it can be overridden. First, you must create a subclass of the aforementioned editing control, overriding the OnSelectedIndexChanged method:
protected override void OnSelectedIndexChanged(EventArgs e) {
base.OnSelectedIndexChanged(e);
EditingControlValueChanged = true;
EditingControlDataGridView.NotifyCurrentCellDirty(true);
EditingControlDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
This will ensure that the DataGridView is properly notified of the change in item selection in the combo box when it takes place.
You then need to subclass DataGridViewComboBoxCell and override the EditType property to return the editing control subclass from above (e.g. return typeof(MyEditingControl);). This will ensure that the correct editing control is created when the cell goes into edit mode.
Finally, you can set the CellTemplate property of your DataGridViewComboBoxColumn to an instance of the cell subclass (e.g. myDataGridViewColumn.CellTemplate = new MyCell();). This will ensure that the correct type of cell is used for each row in the grid.

I tried using Bradley's suggestion, but it was sensitive to when you attached the cell template. It seemed like I couldn't allow the design view to wire up the column, I had to do it myself.
Instead, I used the binding source's PositionChanged event, and triggered updates from that. It's a little bit odd, because the control is still in edit mode, and the databound object doesn't get the selected value yet. I just updated the bound object myself.
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
(MyBoundType)bindingSource.Current.MyBoundProperty =
((MyChoiceType)comboBindingSource.Current).MyChoiceProperty;
}

A better way to achieve this that I am using successfully rather than subclassing or the somewhat inelegant binding source method above, is the following (sorry it's VB but if you can't translate from VB to C# you have bigger problems :)
Private _currentCombo As ComboBox
Private Sub grdMain_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles grdMain.EditingControlShowing
If TypeOf e.Control Is ComboBox Then
_currentCombo = CType(e.Control, ComboBox)
AddHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
End If
End Sub
Private Sub grdMain_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdMain.CellEndEdit
If Not _currentCombo Is Nothing Then
RemoveHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
_currentCombo = Nothing
End If
End Sub
Private Sub SelectionChangedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myCombo As ComboBox = CType(sender, ComboBox)
Dim newInd As Integer = myCombo.SelectedIndex
//do whatever you want with the new value
grdMain.NotifyCurrentCellDirty(True)
grdMain.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
That's it.

Related

Setting IsChecked property of programmatically created RadioButtons

I have a bunch of programmatically-created RadioButtons that are displayed in a StackPanel.
I want to use some on-screen buttons or the arrow keys to navigate these RadioButtons, and set the IsChecked property to True when landing on them.
I'm just not sure how to reference these RadioButtons?
Any direction would be appreciated, thanks!
When you dynamically add the controls you will want to store them in a private member variable for reference, eg Private ctrls As New List<Controls>() and then in the Key_Down event set the ctrl[0].IsChecked.
Private Sub Window_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
If e.Key = Key.Down Then
Dim ctrlIndex As Integer = ControlIdWithFocus()
ctrls(ctrlIndex).IsChecked = True
End If
End Sub
Alternatively you could iterate through the Me.Controls collection, although that will contain all the controls not just the Radio buttons.

Name of control in routed event

Isn't there a way to tell what control fired a routed event? I have a SelectionChangedEvent for use by a combobox on a radgridview. I want the coding in that event to handle only that combobox and no others. I tried using e.OriginalSource.Name, ToString, sender.ToString, sender.Name but all return "". So there's no way to tell what combobox is being processed by the event.
Code to create event:
Me.AddHandler(RadComboBox.SelectionChangedEvent, New System.Windows.Controls.SelectionChangedEventHandler(AddressOf FinishedEndsChanged))
Code inside event:
Private Sub FinishedEndsChanged(ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
Try
If dgChosenItems.SelectedItems.Count > 0 Then
Dim comboBox As RadComboBox = CType(e.OriginalSource, RadComboBox)
If comboBox.SelectedValue IsNot Nothing Then
Dim endChosen As String = CStr(comboBox.SelectedValue)
Thanks.
Give your ComboBox a name so you can address it in sourcecode-behind by this unique name. Check
If e.OriginalSource == _youridhere_ Then // If sender == ... should work as well
// do what you must
Not very good style and probably only feasable for one to a few boxes...
I believe I found the answer here:
[https://www.telerik.com/forums/selection-changed-event-for-gridviewcombobox-column]
I chose to use SelectedValudPath.
Thanks, Patrick, for taking the time to reply.

Need to determine which grid a double-click came from?

I think this should be fairly simple, but I've been looking through the properties in the signature for the handler that I'm using and I don't see any way to suss out what I'm looking for.
I have a fairly simple WPF app with two DataGrid controls in the same window. I have a double click event defined in the XAML like so:
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<EventSetter
Event="MouseDoubleClick"
Handler="Row_DoubleClick"/>
</Style>
</DataGrid.ItemContainerStyle>
And in the code behind (do we call it that in WPF apps?) I have the Row_DoubleClick handler set up like so:
Private Sub Row_DoubleClick(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Now the sub itself works fine and picks up the row that was double-clicked just fine. However, as I noted before I have two DataGrids that use this same sub for the double-click event. I realize one path might be to simply make two subs, but it seems like I should be able to use the one for both, and it's taking the exact same action in either case, just using the row from one DataGrid or the other.
It always defaults to the first, let's call it IncompleteGrid, if a row is selected even if the second DataGrid, let's call it CompleteGrid, is the the one being double clicked. I've been looking through the sender and e objects in debug mode, but I don't see any place or property I can check to see which grid the double-click is coming from.
Any ideas?
You can get the parent dataGrid from row by using VisualTreeHelper. Have this private method on your code (code is in C#, hope you can get it convert to VB easily):
private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
{
DataGridRow row = sender as DataGridRow;
DataGrid senderDataGrid = FindAncestor<DataGrid>(row);
}
private T FindAncestor<T>(DependencyObject dependencyObject)
where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindAncestor<T>(parent);
}
VB Version:
Private Sub Row_DoubleClick(sender As Object, e As MouseButtonEventArgs)
Dim row As DataGridRow = TryCast(sender, DataGridRow)
Dim senderDataGrid As DataGrid = FindAncestor(Of DataGrid)(row)
End Sub
Private Function FindAncestor(Of T As DependencyObject)(dependencyObject As DependencyObject) As T
Dim parent = VisualTreeHelper.GetParent(dependencyObject)
If parent Is Nothing Then
Return Nothing
End If
Dim parentT = TryCast(parent, T)
Return If(parentT, FindAncestor(Of T)(parent))
End Function
This parameter should give you the information:
ByVal sender As System.Object
sender should be the grid that the double-click is coming from. (That's the meaning of sender -- the control that sent the event.)
You can cast sender to a DataGrid if you want to do specific stuff with it.
Edit: If sender is a DataGridRow instead of a DataGrid, then you could use this question to find the host DataGrid. (Using a RelativeSource or a CommandParameter seems to the accepted methods for this.)

Datagrid operation is not valid while itemssource is in use

I want to delete the records of datagrid by using code and update the database.
i use the following code for deleting records from datagrid datagrid1.items.RemoveAt(0),
and this line give a Error msg:
operation is not valid while itemssource is in use
But if i delete the record of datagrid using keyboard delete button and then pressing btnUpdate_Click for updating the database, then it works fine without no error.
so please give me the proper reason why this happen.
I hope you all understand my problem.
My All code is here:
Dim db As New dbconnect()
Dim cmd As MySqlCommand
Dim cmdBuild As MySqlCommandBuilder
Dim da As MySqlDataAdapter
Dim dt As New DataTable
Private Sub btn_DayBook_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btn_DayBook.Click
cmd = New MySqlCommand("select * from transactions", db.conn)
dt = New DataTable()
da = New MySqlDataAdapter(cmd)
da.Fill(dt)
DataGrid1.DataContext = dt
DataGrid1.ItemsSource = dt.DefaultView
cmdBuild = New MySqlCommandBuilder(da)
End Sub
Private Sub btnUpdate_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnUpdate.Click
Try
cmdBuild.GetUpdateCommand()
da.Update(dt)
btn_DayBook_Click(sender, e)
MsgBox("Record Updated")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnDelete.Click
Try
DataGrid1.Items.RemoveAt(DataGrid1.SelectedIndex)
DataGrid1.Items.Refresh()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
Try
cmdBuild.GetDeleteCommand()
da.Update(dt)
MsgBox("Record Deleted")
Catch ex As Exception
MsgBox(ex.ToString)
End Try
btn_DayBook_Click(sender, e)
End Sub
In WPF, collection controls provide two main ways to set or access data. When setting data, you can either use the ItemsSource property or you can use the Items property. If you use the ItemsSource property as you have, then to manipulate the items in the collection control, you simply manipulate the items from the data collection that is data bound to that control.
In your case, you could maintain a reference to your DataTable and alter its rows and columns to update the UI. However, when editing data in this way, you need a way for changes to be updated in the UI. Because of this, we generally create custom classes that represent our data and implement the INotifyPropertyChanged Interface.
Then we would iterate through our incoming data, row by row, populating an ObservableCollection<YourDataType> with data. This would then be data bound to the ItemsSource property of the collection control and changes could then be made by editing the items in the data bound collection. Using this method is often combined with data binding the itemsControl.SelectedItem property to an object of type YourDataType in your view model, or code behind. This is the preferred way to work with collection controls in WPF.
if would be easier if do a little bit more wpf with binding and mvvm
you would have a collection (at best OberservableCollection of your Datatype)
you would bind this collection as ItemsSource to your datagrid
you just alter your collection with Clear(), Add(), Remove() and your grid is "magically" display this :)
If you attach an itemsource to the datagrid then you need to modify the collection you have used as an itemsource not in the way you have done above.
When you press delete on the keyboard the datagrid is handling this for you and deleting the item from your itemsource instead of the datagrids items.

Handling events from user control containing a WPF textbox

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.
Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp
ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here
Seems basic, but did you set the KeyPreview to TRUE on your form?

Resources