Retrieve Value on Mouse Click for WPF Column Series - wpf

I have a simple chart with two column series containing all months in the year. I want to filter a list view that show detailed information for the selected month. I can capture the event via MouseDown on the ColumnSeries but I'm not sure how to get to the month in the column series.
<DVC:ColumnSeries Title=" Expenditures" IndependentValueBinding="{Binding Path=Month}"
DependentValueBinding="{Binding Path=Amt}"
ItemsSource="{Binding Path=ActivityExpenditureSeries}"
MouseDown="ColumnSeries_MouseDown" />
I'm sure I could do some fancy WPF databinding to the selected ColumnSeries for the listviews ItemsSource but this is where I'm heading:
Private Sub ColumnSeries_MouseDown(ByVal sender As System.Object,
ByVal e As System.Windows.Input.MouseButtonEventArgs)
' This is the functionality I'm looking for...
Dim selectedColumn As String
FilterListView(selectedColumn)
End Sub

Set the IsSelectionEnabled=True on the series and added a SelectionChanged event to the same series.
Private Sub colSeries_adjExpenditure_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
Dim cs As ColumnSeries = CType(sender, ColumnSeries)
Dim dp As MyDataPoint = CType(cs.SelectedItem, MyDataPoint)
End Sub

Set the IsSelectionEnabled=True on the series and added a SelectionChanged event to the same series.
System.Windows.Controls.DataVisualization.Charting.ColumnSeries cs = (System.Windows.Controls.DataVisualization.Charting.ColumnSeries)sender;
System.Data.DataRowView dp = (System.Data.DataRowView)cs.SelectedItem;
tbkName.Text = dp.Row[1].ToString();
tbkSalary.Text = dp.Row[0].ToString();

Example in C#:
Set the IsSelectionEnabled=True on the series and added a SelectionChanged event to the same series.
Name Space:
using System.Windows.Controls.DataVisualization.Charting;
Method:
private void ColumnSeries_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ColumnSeries cs = (ColumnSeries)sender;
KeyValuePair<string, int> kv = (KeyValuePair<string, int>)cs.SelectedItem;
Debug.WriteLine(kv.Key);
Debug.WriteLine(kv.Value);
}

[In C#]
Previous answers only allow clicking when selections are changed. Following code will enable clicking on columns independently of where you clicked earlier. It will also allow right clicking if needed (change event type)
<chartingToolkit:ColumnSeries DependentValuePath="Value" IndependentValuePath="Key" IsSelectionEnabled="True">
<chartingToolkit:ColumnSeries.DataPointStyle>
<Style TargetType="chartingToolkit:ColumnDataPoint">
<EventSetter Event="MouseLeftButtonUp" Handler="ColumnSeries_ColumnLeftClicked"/>
</Style>
</chartingToolkit:ColumnSeries.DataPointStyle>
</chartingToolkit:ColumnSeries>
private void ColumnSeries_ColumnLeftClicked(object sender, MouseButtonEventArgs e)
{
var key = ((ColumnDataPoint)sender).IndependentValue;
//etc
}

Related

How to change the string format of a datagrid column in WPF with vb.net programmatically?

Basically, what I want to do is the WinForm Datagridview equivalent of dgvPreview.Columns(4).DefaultCellStyle.Format = "#,##0.00"
But instead of Datagridview, it's with Datagrid in WPF. Best I can do is assign a datatable to a Datagrid and change its alignment property.
DataGridPreview.ItemsSource = dtPreview.DefaultView
Private Sub BtnTest_Click(sender As Object, e As RoutedEventArgs) Handles BtnTest.Click
Dim txt As New DataGridTextColumn()
Dim s As New Style
s.Setters.Add(New Setter(TextBox.TextAlignmentProperty, TextAlignment.Right))
txt.CellStyle = s
DataGridPreview.Columns(4).CellStyle = s
'dgvPreview.Columns(4).DefaultCellStyle.Format = "#,##0.00"
End Sub
Please point me in the right direction. I'm trying to migrate from Winforms to WPF. And as much as possible I want to do this programmatically. I have also tried using the AutoGeneratingColumn but I can't figure it out.
If e.Column.Header.ToString = "Amount" Then
Dim dg As DataGridTextColumn = e.Column
dg.Binding.StringFormat = "#,000.00"
End If
If you are using AutoGeneratingColumn, the best time to update the StringFormat is on AutogeneratingColumn event. Column's binding serves as a blueprint for the individual cells' binding, so for some updates it is important to do them before the cells are created. In C# it will be something like this:
public MainWindow()
{
InitializeComponent();
grid.ItemsSource = Enumerable.Range(0, 10).Select(s => new { Id = s });
}
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
(e.Column as DataGridTextColumn).Binding.StringFormat = "0.000";
}

Scroll WPF DataGrid to show selected item on top

I have a DataGrid with many items and I need to programmatically scroll to the SelectedItem. I have searched on StackOverflow and Google, and it seems the solution is ScrollIntoView, as follows:
grid.ScrollIntoView(grid.SelectedItem)
which scrolls the DataGrid up or down until the selected item is in focus. However, depending on the current scroll position relative to the selected item, the selected item may end up being the last visible item in the DataGrid's ScrollViewer. I want that the selected item will be the first visible item in the ScrollViewer (assuming there are enough rows in the DataGrid to allow this). So I tried this:
'FindVisualChild is a custom extension method that searches in the visual tree and returns
'the first element of the specified type
Dim sv = grid.FindVisualChild(Of ScrollViewer)
If sv IsNot Nothing Then sv.ScrollToEnd()
grid.ScrollIntoView(grid.SelectedItem)
First I scroll to the end of the DataGrid and only then do I scroll to the SelectedItem, at which point the SelectedItem is shown at the top of the DataGrid.
My problem is that scrolling to the end of the DataGrid works well, but subsequently scrolling to the selected item doesn't always work.
How can I resolve this issue, or is there any other alternative strategy for scrolling to a specific record in the top position?
You were on the right track, just try to work with collection view instead of working directly on the datagrid for this kind of needs.
Here is a working example where the desired item is always displayed as first selected item if possible, otherwise the scrollviewer is scrolled to the end and the target item is selected at its position.
The key points are :
Use CollectionView on the business side and enable current item synch on the XAML control (IsSynchronizedWithCurrentItem=true)
Defer the "real" target scroll in order to allow the "Select Last item" to be visualy executed (By using a Dispatcher.BeginInvoke with a low priority)
Here is the business logic (This is automatic convertion from C# to VB)
Public Class Foo
Public Property FooNumber As Integer
Get
End Get
Set
End Set
End Property
End Class
Public Class MainWindow
Inherits Window
Implements INotifyPropertyChanged
Private _myCollectionView As ICollectionView
Public Sub New()
MyBase.New
DataContext = Me
InitializeComponent
MyCollection = New ObservableCollection(Of Foo)
MyCollectionView = CollectionViewSource.GetDefaultView(MyCollection)
Dim i As Integer = 0
Do While (i < 50)
MyCollection.Add(New Foo)
i = (i + 1)
Loop
End Sub
Public Property MyCollectionView As ICollectionView
Get
Return Me._myCollectionView
End Get
Set
Me._myCollectionView = value
Me.OnPropertyChanged("MyCollectionView")
End Set
End Property
Private Property MyCollection As ObservableCollection(Of Foo)
Get
End Get
Set
End Set
End Property
Private Sub ButtonBase_OnClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim targetNum As Integer = Convert.ToInt32(targetScroll.Text)
Dim targetObj As Foo = Me.MyCollection.FirstOrDefault(() => { }, (r.FooNumber = targetNum))
'THIS IS WHERE THE MAGIC HAPPENS
If (Not (targetObj) Is Nothing) Then
'Move to the collection view to the last item
Me.MyCollectionView.MoveCurrentToLast
'Bring this last item into the view
Dim current = Me.MyCollectionView.CurrentItem
itemsContainer.ScrollIntoView(current)
'This is the trick : Invoking the real target item select with a low priority allows previous visual change (scroll to the last item) to be executed
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, New Action(() => { }, Me.ScrollToTarget(targetObj)))
End If
End Sub
Private Sub ScrollToTarget(ByVal targetObj As Foo)
Me.MyCollectionView.MoveCurrentTo(targetObj)
itemsContainer.ScrollIntoView(targetObj)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
If (Not (PropertyChanged) Is Nothing) Then
PropertyChanged?.Invoke(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
And this is the xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="itemsContainer" ItemsSource="{Binding MyCollectionView}" IsSynchronizedWithCurrentItem="True" Margin="2" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FooNumber}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<TextBox x:Name="targetScroll" Text="2" Margin="2"></TextBox>
<Button Content="Scroll To item" Click="ButtonBase_OnClick" Margin="2"></Button>
</StackPanel>
</Grid>
I Solved this question with following code:
public partial class MainWindow:Window
{
private ObservableCollection<Product> products=new ObservableCollection<Product> ();
public MainWindow()
{
InitializeComponent ();
for (int i = 0;i < 50;i++)
{
Product p=new Product { Name="Product "+i.ToString () };
products.Add (p);
}
lstProduct.ItemsSource=products;
}
private void lstProduct_SelectionChanged(object sender,SelectionChangedEventArgs e)
{
products.Move (lstProduct.SelectedIndex,0);
lstProduct.ScrollIntoView (lstProduct.SelectedItem);
}
}
public class Product
{
public string Name { get; set; }
}
<Grid>
<ListBox Name="lstProduct" Margin="20" DisplayMemberPath="Name" SelectionChanged="lstProduct_SelectionChanged" />
</Grid>
The accepted answer to this other question shows a different approach to get the first/last visible row of such a grid.
You could find out the index of your row and directly scroll there or scroll down row by row until the first visible row matches.

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

WPF How do I find which ListBox item was clicked

I have a WPF application in which there's a listbox filled with items of type 'Match'.
How do I make the button(contained within the item) actually select the item so that I might extract the value?
Here is my code: neither works since clicking the button doesn't actually select the item
private void LayButton_Click(object sender, RoutedEventArgs e)
{
var x = (Market)ListBoxSelectedMarket.SelectedItem;
var y = (sender as ListBoxItem);
}
Thanks
You should be able to use the DataContext from the clicked Button and get the ListBoxItem container from there, and then select it.
private void LayButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
var dataContext = button.DataContext;
ListBoxItem clickedListBoxItem = ListBoxSelectedMarket.ItemContainerGenerator.ContainerFromItem(dataContext) as ListBoxItem;
clickedListBoxItem.IsSelected = true;
}
If you are binding to an object an alternative method could be (in VB)
This then gives you an instance of your object to play with and saves you having any mapping fields on the listbox
Private Sub OTC_Settled_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim pr_YourObject As New YourObject
Dim btn As Button = CType(sender, Button)
OTC = DirectCast(btn.DataContext, pr_YourObject)
End Sub
I haven't done much WPF programming, but you could try getting the parent of the button if it works the same as a WinForms container object.

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