WPF refresh TreeView when it loses the focus - wpf

I have a problem with my TreeView in a WPF application (Framework 3.5 SP1).
It's a TreeVIew with 2 Levels of Data. I expand / collapse the items of the first level in a particular way (with a single mouse-click on the TreeViewItem). Again when I expand a first-level TreeViewItem, I add some second-level TreeViewItems to the group (it's an important detail, infact if I don't add the items the problem doesn't occur). All works good until the TreeView loses focus.
If, for example, I expand the TreeViewItem at the first position, adding at the same time one element to the second-level, then I click on a button (to let the TreeView lose the focus), and then I click again on the TreeViewItem at the third position to expand it, the TreeViewItem that results from the hit-test with the mouse position is not the "real" TreeViewItem (in this case the third), but a TreeViewItem which is in an higher position than the one clicked (in this case the second).
I have tried to use the UpdateLayout method on the TreeView-LostFocus event, but without results. Probably I need a method that does the opposite: starting from the UI, refresh the object that contains the position of the TreeViewItems.
Can you, please, help me?
Thank you!
Pileggi
This is the code:
' in this way I tried to put remedy at the problem, but it doesn't work.
Private Sub tvArt_LostFocus(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles tvArt.LostFocus
Me.tvArt.UpdateLayout()
e.Handled = True
End Sub
' here I expand / collapse the items of the first level of my TreeView
Private Sub tvArt_PreviewMouseUp(ByVal sender As System.Object, ByVal e As MouseButtonEventArgs) Handles tvArt.PreviewMouseUp
Dim p As Point = Nothing
Dim tvi As TreeViewItem = getItemFromMousePosition(Of TreeViewItem)(p, e.OriginalSource, Me.tvArt)
If tvi Is Nothing = False Then
If tvi.HasItems Then
Dim be As BindingExpression = BindingOperations.GetBindingExpression(tvi, TreeViewItem.ItemsSourceProperty)
Dim ri As P_RicambiItem = DirectCast(be.DataItem, P_RicambiItem)
If ri.isExpanded = False then
' here I add items to the second level collection
End If
ri.isExpanded = Not ri.isExpanded
End If
End If
e.Handled = True
End Sub
Private Function getItemFromMousePosition(Of childItem As DependencyObject)(ByRef p As Point, ByVal sender As UIElement, _
ByVal _item As UIElement) As childItem
p = sender.TranslatePoint(New Point(0, 0), _item)
Dim obj As DependencyObject = DirectCast(_item.InputHitTest(p), DependencyObject)
While obj Is Nothing = False AndAlso TypeOf obj Is childItem = False
obj = VisualTreeHelper.GetParent(obj)
End While
Return DirectCast(obj, childItem)
End Function

I have find this solution (but I don't like it very much). The problem is depending from the added items that wpf, for some reasons, doesn't remember that exist. Then I do a "manual" refresh with a method that clear and re-add all the items in the source-collection:
Public Sub RefreshData(ByVal RicambiListPass As ObservableCollection(Of P_RicambiItem))
Dim l As New List(Of P_RicambiItem)
l.AddRange(RicambiListPass)
_RicambiList.Clear()
For Each i As P_RicambiItem In l
_RicambiList.Add(i)
Next
End Sub

Related

WPF TreeView Selecteditem. Issue with adding child Items (VB,net)

This may have a very simple solution but I am pretty new to this. I am trying add child items into a selected treeView item with the click of a button. Code works fine with nothing selected but seems Treeview1.selectedItem doesn't have an .Add method.
Imports System.Windows.Controls.TreeView
Class MainWindow
Private Sub addNodeButton_Click(sender As Object, e As RoutedEventArgs) Handles addNodeButton.Click
Dim n As String = "Model"
If TreeView1.SelectedItem Is Nothing Then
TreeView1.Items.Add(n)
Else
TreeView1.SelectedItem.add("test")
End If
End Sub
End Class
Any help appreciated.
Ok. I knew it would turn out to be simple. I was initially adding items as Strings insead of TreeViewItems and thus couldn't add children. Fixed code below:
Private Sub addNodeButton_Click(sender As Object, e As RoutedEventArgs) Handles addNodeButton.Click
Dim n As New TreeViewItem
n.Header = "Model"
If TreeView1.SelectedItem Is Nothing Then
TreeView1.Items.Add(n)
Else
Dim tempitem As New TreeViewItem
tempitem = TreeView1.SelectedItem
Dim newitem As New TreeViewItem
newitem.Header = "test"
tempitem.Items.Add(newitem)
End If
End Sub

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.)

Apply and validate a bound DataGridViewComboBoxCell directly upon selection change

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.

wpf forcing update UI window during a procedure

I need only to show a custom control (a clock with rotating hands) and with this to replace the mouse cursor, if I use a file .cur or .ani to replace the mouse cursor
Me.CUrsor = New Cursor("absolute path of the .ani file")
there is no problem: I can change the cursor during a procedure: but the quality of the animation is very bad, and, also for other reasons, I'd prefer to use my little user-control. The problem is that if I write:
Me.gridScreen.Visibility = Visibility.Visible
' some operations that takes about 1 second
Me.gridScreen.Visibility = Visibility.Hidden
(gridScreen is the grid that contains the user-control)
Obviously I can see nothing, because the update of the UI happens at the end of the procedure. I have tried Me.UpdateLayout(), but it doesn't work.
I have tryed to use the dispacker in many way but none that works :-(
This is my lost attempt:
(uCurClock is the usercontrol, gridScreen a Grid placed at the top-level in the window, with trasparent background, that contains the usercontrol)
Private Sub showClock()G
Dim thread = New System.Threading.Thread(AddressOf showClockIntermediate)
thread.Start()
End Sub
Private Sub hideClock()
Dim thread = New System.Threading.Thread(AddressOf hideClockIntermediate)
thread.Start()
End Sub
Private Sub showClockIntermediate()
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New Action(AddressOf showClockFinale))
End Sub
Private Sub hideClockIntermediate()
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New Action(AddressOf hideClockFinale))
End Sub
Private Sub showClockFinale()
Dim pt As Point = Mouse.GetPosition(Nothing)
Me.uCurClock.Margin = New Thickness(pt.X - 9, pt.Y - 9, 0, 0)
Me.gridScreen.Visibility = Visibility.Visible
Me.Cursor = Cursors.None
Me.UpdateLayout()
End Sub
Private Sub hideClockFinale()
Me.gridScreen.Visibility = Visibility.Hidden
Me.Cursor = Cursors.Arrow
Me.UpdateLayout()
End Sub
Private Sub u_MouseMove(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseMove
Dim pt As Point = e.GetPosition(Nothing)
Me.uCurClock.Margin = New Thickness(pt.X - 9, pt.Y - 9, 0, 0)
e.Handled = True
End Sub
Private Sub u_MouseEnter(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseEnter
Me.uCurClock.Visibility = Visibility.Visible
e.Handled = True
End Sub
Private Sub u_MouseLeave(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseLeave
Me.uCurClock.Visibility = Visibility.Hidden
e.Handled = True
End Sub
PIleggi
While the following code will do what you ask for, I suspect it won't actually help you, since you've mentioned animation. You're going to need to use multiple threads. However, just to demonstrate why that is, here's something that answers the question you've asked:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
uc1.Visibility = Visibility.Visible
Cursor = Cursors.Wait
' Push visibility changes now.
' (Sort of like DoEvents - and a horrible idea for exactly the same
' reasons that DoEvents was a total train wreck. Never actually do
' this - use a background thread instead.)
Dim df As New DispatcherFrame(True)
Dispatcher.BeginInvoke(Sub() df.Continue = False, DispatcherPriority.ContextIdle)
Dispatcher.PushFrame(df)
Thread.Sleep(1000)
ClearValue(CursorProperty)
uc1.Visibility = Visibility.Hidden
End Sub
Assuming you have some usercontrol called uc1 on the page, this will force it to be visible while your slow procedure runs.
But no animations will run. The problem is, if you're doing something slow on the UI thread, the UI thread can't do anything else - it can't run animations, it can't respond to user input. Basically the UI is frozen out. The only reason the code shown here even makes the user control visible is that it basically says "do any outstanding UI thread work now", which has the side effect of processing your change to the Visible property.
But animations happen on the UI thread too.
If you want to do this properly, you need to do the work on a background thread, possibly by using the BackgroundWorker, or by writing your own threading code.
reference DispatcherFrame Class Reference
good ole DoEvents for WPF!!!
Public Sub DoEvents()
Dim frame As New DispatcherFrame()
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, New DispatcherOperationCallback(AddressOf ExitFrame), frame)
Dispatcher.PushFrame(frame)
End Sub
Public Function ExitFrame(ByVal f As Object) As Object
CType(f, DispatcherFrame).Continue = False
Return Nothing
End Function

WPF refresh TreeView when it loses the focus

Because of the changes I have done to my post I have thinked to open another thread. In the new thread I have posted my (provvisory) solution.
You can find it here
Hi!
I have a problem with my TreeView in a WPF application (Framework 3.5 SP1).
It's a TreeVIew with 2 Levels of Data. I expand / collapse the items of the first level in a particular way (with a single mouse-click on the TreeViewItem). Again when I expand a first-level TreeViewItem, I add some second-level TreeViewItems to the group (it's an important detail, infact if I don't add the items the problem doesn't occur). All works good until the TreeView loses focus.
If, for example, I expand the TreeViewItem at the first position, adding at the same time one element to the second-level, then I click on a button (to let the TreeView lose the focus), and then I click again on the TreeViewItem at the third position to expand it, the TreeViewItem that results from the hit-test with the mouse position is not the "real" TreeViewItem (in this case the third), but a TreeViewItem which is in an higher position than the one clicked (in this case the second).
I have tried to use the UpdateLayout method on the TreeView-LostFocus event, but without results. Probably I need a method that does the opposite: starting from the UI, refresh the object that contains the position of the TreeViewItems.
Can you, please, help me?
Thank you!
Pileggi
This is the code:
' in this way I tried to put remedy at the problem, but it doesn't work.
Private Sub tvArt_LostFocus(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles tvArt.LostFocus
Me.tvArt.UpdateLayout()
e.Handled = True
End Sub
' here I expand / collapse the items of the first level of my TreeView
Private Sub tvArt_PreviewMouseUp(ByVal sender As System.Object, ByVal e As MouseButtonEventArgs) Handles tvArt.PreviewMouseUp
Dim p As Point = Nothing
Dim tvi As TreeViewItem = getItemFromMousePosition(Of TreeViewItem)(p, e.OriginalSource, Me.tvArt)
If tvi Is Nothing = False Then
If tvi.HasItems Then
Dim be As BindingExpression = BindingOperations.GetBindingExpression(tvi, TreeViewItem.ItemsSourceProperty)
Dim ri As P_RicambiItem = DirectCast(be.DataItem, P_RicambiItem)
If ri.isExpanded = False then
' here I add items to the second level collection
End If
ri.isExpanded = Not ri.isExpanded
End If
End If
e.Handled = True
End Sub
Private Function getItemFromMousePosition(Of childItem As DependencyObject)(ByRef p As Point, ByVal sender As UIElement, _
ByVal _item As UIElement) As childItem
p = sender.TranslatePoint(New Point(0, 0), _item)
Dim obj As DependencyObject = DirectCast(_item.InputHitTest(p), DependencyObject)
While obj Is Nothing = False AndAlso TypeOf obj Is childItem = False
obj = VisualTreeHelper.GetParent(obj)
End While
Return DirectCast(obj, childItem)
End Function
Your hit test code seems a little odd. You ignore the mouse position given by the MouseButtonEventArgs object, and then do a hit test in the TreeView against the upper-left corner of the control that was clicked. This will normally give you back the same control again, and I suspect your weird behavior is in the cases where it doesn't. Instead of doing TranslatePoint and InputHitTest, just use the sender directly. Your helper function reduces to:
Private Function getParentOfType(Of childItem As DependencyObject)(ByVal sender As UIElement) As childItem
Dim obj As DependencyObject = sender
While obj Is Nothing = False AndAlso TypeOf obj Is childItem = False
obj = VisualTreeHelper.GetParent(obj)
End While
Return DirectCast(obj, childItem)
End Function
You can actually make it simpler again by taking advantage of the fact that MouseUp is a routed event and letting it find the TreeViewItem parent for you. Instead of adding the event handler to the TreeView itself, add a MouseUp handler to the TreeViewItem, and it will always be called with a sender of the TreeViewItem.
You should also set your binding on IsExpanded to be two-way if it is not already. That way you can update IsExpanded on the TreeViewItem and the value will be pushed to the binding source.
In XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding isExpanded, Mode=TwoWay}" />
<EventSetter Event="Mouse.MouseUp" Handler="tvi_MouseUp"/>
</Style>
</TreeView.ItemContainerStyle>
Then in code:
Private Sub tvi_MouseUp(ByVal sender As System.Object, ByVal e As MouseButtonEventArgs)
Dim tvi As TreeViewItem = DirectCast(sender, TreeViewItem)
If tvi.HasItems Then
tvi.IsExpanded = Not tvi.IsExpanded
End If
e.Handled = True
End Sub
Thank you, you are very kind. But unfortunately the problem is the same with your solution.
I omitted an important detail (sorry): when I expand a first-level TreeViewItem I add some second-level TreeviewItems. This causes the problem, if I don't add the items all works good.
I have edited my question, to make it more comprehensible.
Maybe now the solution is more easy (I hope).
Thanks,
Pileggi
Because of the changes I have done to my post I have thinked to open another thread. In the new thread I have posted my (provvisory) solution. You can find it here

Resources