Zooming To Mouse Point With ScrollView and ViewBox in Wpf - wpf

I have some paths drawn to the screen in wpf. The coordinates being used are quite small so they have been made to fill the screen with a view box. I am now trying to implement pan and zoom functionality. I would like to be able to zoom to wherever the mouse is in relation to the ui (i.e zoomed screen center is equal to mouse coordinates). The current outcome is that the center of the screen when zoomed is not reflective of the exact mouse position on the ui.
If you want to see what is happening... Here is my current solution file.
Or
Heres some code:
View Xaml
<Grid Name="MasterGrid" DataContext="{StaticResource mainWindowViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" Name="VisualisationScroller">
<Viewbox Name="VisualisationBox" Stretch="Fill" Loaded="VisualisationBox_Loaded">
<ItemsControl Name="CustomDrawingElement" ItemsSource="{Binding Trajectories}" Width="{Binding VisualisationWidth}" Height="{Binding VisualisationHeight}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="data:VisualisedTrajectory">
<Path Data = "{Binding PathData}" Stroke="Red" StrokeThickness="0.001" Fill="Transparent" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="DarkGray"
IsItemsHost="True">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<TranslateTransform />
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
</Viewbox>
</ScrollViewer>
</Grid>
View Model
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel()
{
VisualiseRawTrajectories();
}
private ObservableCollection<VisualisedTrajectory> _trajectories = new ObservableCollection<VisualisedTrajectory>();
public ObservableCollection<VisualisedTrajectory> Trajectories
{
get { return _trajectories; }
}
#region VisualisationDimensions
private double _visualisationWidth = 100;
public double VisualisationWidth
{
get { return _visualisationWidth; }
private set { _visualisationWidth = value; }
}
private double _visualisationHeight = 100;
public double VisualisationHeight
{
get { return _visualisationHeight; }
private set { _visualisationHeight = value; }
}
#endregion
public void VisualiseRawTrajectories()
{
var rand = new Random();
for (int i = 0; i < 5; i++)
{
var currentTrajectorySet = new List<Point>(); //each time through reinitialise
for (int j = 0; j < 5; j++)
{
currentTrajectorySet.Add(new Point(rand.NextDouble() * 0.5, rand.NextDouble() * 0.5)); //add a new point with max 0.5
if(j == 4)
{
currentTrajectorySet.Add(new Point(0.5, 0.5)); //for good measure :)
_trajectories.Add(new VisualisedTrajectory(CreatePathData(currentTrajectorySet)));
}
}
}
VisualisationHeight = 0.5;
VisualisationWidth = 0.5; //just for demonstration purposes
OnPropertyChanged("VisualisationHeight");
OnPropertyChanged("VisualisationWidth");
}
private Geometry CreatePathData(IList<Point> points)
{
var geometry = new StreamGeometry {FillRule = FillRule.EvenOdd};
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], false, false); //use the first index
ctx.PolyLineTo(points, true, true);
}
return (Geometry)geometry.GetAsFrozen();
}
}
View Code Behind
public MainWindow()
{
InitializeComponent();
VisualisationScroller.PreviewMouseWheel += OnPreviewMouseWheel;
}
private Point originalDimensions;
private void VisualisationBox_Loaded(object sender, RoutedEventArgs e)
{
Viewbox viewBox = sender as Viewbox;
viewBox.Width = viewBox.ActualWidth;
viewBox.Height = viewBox.ActualHeight;
originalDimensions = new Point(viewBox.ActualWidth, viewBox.ActualHeight);
}
#region Zoom
private int _numberDrawnItems = 0;
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var zoomScale = new Point(CustomDrawingElement.RenderTransform.Value.M11,
CustomDrawingElement.RenderTransform.Value.M22); //gets the scale x and scale y
if (CustomDrawingElement != null && _numberDrawnItems != CustomDrawingElement.Items.Count) //if there was something draw to screen
{
_numberDrawnItems = CustomDrawingElement.Items.Count;
CustomDrawingElement.RenderTransformOrigin = new Point(0.5, 0.5); //if not set zoom from center
}
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = originalDimensions.X * (zoomScale.X + 1);
VisualisationBox.Height = originalDimensions.Y * (zoomScale.Y + 1);
var mousePosition = e.GetPosition(MasterGrid);
CustomDrawingElement.RenderTransformOrigin = new Point(mousePosition.X / MasterGrid.ActualWidth, mousePosition.Y / MasterGrid.ActualHeight);
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X + 1, 0, 0, zoomScale.Y + 1, 0, 0);
}
}
if (e.Delta < 0)
{
if (zoomScale.X > 1 && zoomScale.Y > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = VisualisationBox.Width - originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - originalDimensions.Y;
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X - 1, 0, 0, zoomScale.Y - 1, 0, 0);
}
}
}
e.Handled = true;
}
#endregion //Zooming code
}

Solved it changed the back code for zooming the View to:
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
//zoom
_zoomScale++; //increase it now that we have zoomed
VisualisationBox.Width = _originalDimensions.X * (_zoomScale);
VisualisationBox.Height = _originalDimensions.Y * (_zoomScale);
ScrollerDimensions.Content = VisualisationScroller.ActualWidth + "x" + VisualisationScroller.ActualHeight;
BoxDimensions.Content = VisualisationBox.ActualWidth + "x" + VisualisationBox.ActualHeight;
var mousePosition = e.GetPosition(MasterGrid);
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
ScrolledPoint.Content = mousePosition.X + "," + mousePosition.Y;
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
if (e.Delta < 0)
{
if (_zoomScale > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
var mousePosition = e.GetPosition(MasterGrid);
_zoomScale -= 1; //decrease the zoom
VisualisationBox.Width = VisualisationBox.Width - _originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - _originalDimensions.Y;
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
mousePosition = new Point(mousePosition.X - _originalDimensions.X, mousePosition.Y - _originalDimensions.Y);
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
}
e.Handled = true;
If anyone is interested HERE is the finished solution file with panning implemented too.

Related

Change width of ItemsControl cells on button click

I am trying to create a table whose cells are dynamic in width and height. Following is my xaml code:
<ItemsControl Grid.Row="1" Grid.Column="1" DataContext="{Binding GameBoardApp}" ItemsSource="{Binding BlockList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding Row}"/>
<Setter Property="Grid.Column" Value="{Binding Col}"/>
<Setter Property="Grid.Width" Value="{Binding Width, Mode=TwoWay}" />
<Setter Property="Grid.Height" Value="{Binding Height, Mode=TwoWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<Grid>
<Rectangle Fill="{Binding Background}" />
<TextBlock Text="{Binding Name}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am able to design the UI and see the content but I am not able to set up 2-way data binding to change the width of the cell. Following is my Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GameBoardApp = new GameBoard(50);
DataContext = this;
}
public GameBoard GameBoardApp { get; set; }
private void IncreaseSize_Click(object sender, RoutedEventArgs e)
{
GameBoardApp = new GameBoard(GameBoardApp.CellWidth + 1);
Debug.Print(GameBoardApp.CellWidth.ToString());
}
private void DecreaseSize_Click(object sender, RoutedEventArgs e)
{
GameBoardApp = new GameBoard(GameBoardApp.CellWidth-1);
Debug.Print(GameBoardApp.CellWidth.ToString());
}
}
public class GameBoard : INotifyPropertyChanged
{
public GameBoard(int cellWidthVal)
{
_cellWidth = cellWidthVal;
var m1 = new Block() { Name = "b1", Background = Brushes.Red, Col = 50, Row = 50, Width = CellWidth };
var m2 = new Block() { Name = "b2", Background = Brushes.Gray, Col = 50, Row = 50, Width = CellWidth };
var m3 = new Block() { Name = "b3", Background = Brushes.Goldenrod, Col = 0, Row = 0, Width = CellWidth };
var m4 = new Block() { Name = "b4", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m5 = new Block() { Name = "b5", Background = Brushes.Bisque, Col = 70, Row = 70, Width = CellWidth };
var m6 = new Block() { Name = "b6", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m7 = new Block() { Name = "b7", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m8 = new Block() { Name = "b1", Background = Brushes.Red, Col = 0, Row = 0, Width = CellWidth };
var m9 = new Block() { Name = "b2", Background = Brushes.Gray, Col = 0, Row = 0, Width = CellWidth };
var m10 = new Block() { Name = "b3", Background = Brushes.Goldenrod, Col = 0, Row = 0, Width = CellWidth };
var m11 = new Block() { Name = "b4", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m12 = new Block() { Name = "b5", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m13 = new Block() { Name = "b6", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
var m14 = new Block() { Name = "bhg jg7", Background = Brushes.Honeydew, Col = 0, Row = 0, Width = CellWidth };
_blockList = new ObservableCollection<Block>() { m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11};
}
int _cellWidth;
public int CellWidth { get { return _cellWidth; } set { _cellWidth = value; RaisePropertyChanged("CellWidth"); } }
ObservableCollection<Block> _blockList;
public ObservableCollection<Block> BlockList { get { return _blockList; } set { _blockList = value; RaisePropertyChanged("BlockList"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
public class Block : INotifyPropertyChanged
{
int _row;
public int Row { get { return _row; } set { _row = value; RaisePropertyChanged("Row"); } }
int _col;
public int Col { get { return _col; } set { _col = value; RaisePropertyChanged("Col"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
Brush _background;
public Brush Background { get { return _background; } set { _background = value; RaisePropertyChanged("Background"); } }
int _width = 50;
public int Width { get { return _width; } set { _width = value; RaisePropertyChanged("Width"); } }
int _height = 50;
public int Height { get { return _height; } set { _height = value; RaisePropertyChanged("Width"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
I am trying to refresh the ItemControl once I make the changes in GameBoard but not able to do it.
Preferably I would want to change the width with only one width variable in GameBoard class but looks like that will not be possible here
Regards
Fix your ItemContainerStyle to target the ContentPresenter and bind its Width and Height properties to your source properties:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Width" Value="{Binding Width}" />
<Setter Property="Height" Value="{Binding Height}" />
</Style>
</ItemsControl.ItemContainerStyle>
And since you are creating a new GameBoard object in your click event handlers, you also need to implement the INotifyPropertyChanged interface and raise change notifications in the window:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
GameBoardApp = new GameBoard(50);
DataContext = this;
}
private GameBoard _gameBoardApp;
public GameBoard GameBoardApp
{
get { return _gameBoardApp; }
set { _gameBoardApp = value; RaisePropertyChanged(); }
}
private void IncreaseSize_Click(object sender, RoutedEventArgs e)
{
GameBoardApp = new GameBoard(GameBoardApp.CellWidth + 1);
Debug.Print(GameBoardApp.CellWidth.ToString());
}
private void DecreaseSize_Click(object sender, RoutedEventArgs e)
{
GameBoardApp = new GameBoard(GameBoardApp.CellWidth - 1);
Debug.Print(GameBoardApp.CellWidth.ToString());
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This should make your example work as expected.
As a side note, the Grid.Row and Grid.Column attached properties are not useful when you host your item containers in a WrapPanel.

TextBox doesn't stretch inside a Stackpanel

Please see my attached example code:
<StackPanel Width="300">
<StackPanel Orientation="Horizontal">
<Label Content="Label" Width="100" />
<TextBox Text="Content" />
</StackPanel>
</StackPanel>
How can I make the TextBox stretch to fill all the remaining space horizontally? HorizontalAlignment is not working the way I expect it.
The inner StackPanel will be a UserControl, so I can't just replace both StackPanels with a Grid.
Instead of using the inner StackPanel, you can use a DockPanel.
<StackPanel Width="300">
<DockPanel>
<Label Content="Label" Width="100" />
<TextBox Text="Content" />
</DockPanel>
</StackPanel>
I would recommend using DockPanel as the root control for your UserControl so that you could get the behavior you are looking for in its Child Controls. However if you absolutely need it to be based on a StackPanel then just use some code behind to set the textbox width dynamically.
Xaml:
<StackPanel Name="stpnlOuterControl" Width="300" SizeChanged="StackPanel_SizeChanged">
<StackPanel Orientation="Horizontal">
<Label Content="Test" Width="100"/>
<TextBox Name="txtbxTest" Text="Content"/>
</StackPanel>
</StackPanel>
Code Behind:
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
txtbxTest.Width = stpnlOuterControl.ActualWidth - 100
End Sub
Private Sub StackPanel_SizeChanged(sender As Object, e As SizeChangedEventArgs)
txtbxTest.Width = stpnlOuterControl.ActualWidth - 100
End Sub
Alternative way is to use custom StackPanel
https://github.com/SpicyTaco/SpicyTaco.AutoGrid/blob/master/src/AutoGrid/StackPanel.cs
MainWindow
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:VM="clr-namespace:WpfApplication4" >
<StackPanel Background="Red" Width="300">
<VM:StackPanel Orientation="Horizontal">
<Label Background="Blue" Content="Label" Width="100" />
<TextBox VM:StackPanel.Fill="Fill" Text="Content" />
</VM:StackPanel>
</StackPanel>
</Window>
Code for Custom Stack panel from SpicyTaco
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication4
{
public class StackPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
UIElementCollection children = InternalChildren;
double parentWidth = 0;
double parentHeight = 0;
double accumulatedWidth = 0;
double accumulatedHeight = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, MarginBetweenChildren);
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i];
if (child == null) { continue; }
// Handle only the Auto's first to calculate remaining space for Fill's
if (GetFill(child) != StackPanelFill.Auto) { continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
var childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight));
// Measure child.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// Add all margin to accumulated size before calculating remaining space for
// Fill elements.
if (isHorizontal)
{
accumulatedWidth += totalMarginToAdd;
}
else
{
accumulatedHeight += totalMarginToAdd;
}
var totalCountOfFillTypes = children
.OfType<UIElement>()
.Count(x => GetFill(x) == StackPanelFill.Fill
&& x.Visibility != Visibility.Collapsed);
var availableSpaceRemaining = isHorizontal
? Math.Max(0, constraint.Width - accumulatedWidth)
: Math.Max(0, constraint.Height - accumulatedHeight);
var eachFillTypeSize = totalCountOfFillTypes > 0
? availableSpaceRemaining / totalCountOfFillTypes
: 0;
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i];
if (child == null) { continue; }
// Handle all the Fill's giving them a portion of the remaining space
if (GetFill(child) != StackPanelFill.Fill) { continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
var childConstraint = isHorizontal
? new Size(eachFillTypeSize,
Math.Max(0.0, constraint.Height - accumulatedHeight))
: new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
eachFillTypeSize);
// Measure child.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// Make sure the final accumulated size is reflected in parentSize.
parentWidth = Math.Max(parentWidth, accumulatedWidth);
parentHeight = Math.Max(parentHeight, accumulatedHeight);
var parent = new Size(parentWidth, parentHeight);
return parent;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElementCollection children = InternalChildren;
int totalChildrenCount = children.Count;
double accumulatedLeft = 0;
double accumulatedTop = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var marginBetweenChildren = MarginBetweenChildren;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, marginBetweenChildren);
double allAutoSizedSum = 0.0;
int countOfFillTypes = 0;
foreach (var child in children.OfType<UIElement>())
{
var fillType = GetFill(child);
if (fillType != StackPanelFill.Auto)
{
if (child.Visibility != Visibility.Collapsed && fillType != StackPanelFill.Ignored)
countOfFillTypes += 1;
}
else
{
var desiredSize = isHorizontal ? child.DesiredSize.Width : child.DesiredSize.Height;
allAutoSizedSum += desiredSize;
}
}
var remainingForFillTypes = isHorizontal
? Math.Max(0, arrangeSize.Width - allAutoSizedSum - totalMarginToAdd)
: Math.Max(0, arrangeSize.Height - allAutoSizedSum - totalMarginToAdd);
var fillTypeSize = remainingForFillTypes / countOfFillTypes;
for (int i = 0; i < totalChildrenCount; ++i)
{
UIElement child = children[i];
if (child == null) { continue; }
Size childDesiredSize = child.DesiredSize;
var fillType = GetFill(child);
var isCollapsed = child.Visibility == Visibility.Collapsed || fillType == StackPanelFill.Ignored;
var isLastChild = i == totalChildrenCount - 1;
var marginToAdd = isLastChild || isCollapsed ? 0 : marginBetweenChildren;
Rect rcChild = new Rect(
accumulatedLeft,
accumulatedTop,
Math.Max(0.0, arrangeSize.Width - accumulatedLeft),
Math.Max(0.0, arrangeSize.Height - accumulatedTop));
if (isHorizontal)
{
rcChild.Width = fillType == StackPanelFill.Auto || isCollapsed ? childDesiredSize.Width : fillTypeSize;
rcChild.Height = arrangeSize.Height;
accumulatedLeft += rcChild.Width + marginToAdd;
}
else
{
rcChild.Width = arrangeSize.Width;
rcChild.Height = fillType == StackPanelFill.Auto || isCollapsed ? childDesiredSize.Height : fillTypeSize;
accumulatedTop += rcChild.Height + marginToAdd;
}
child.Arrange(rcChild);
}
return arrangeSize;
}
static double CalculateTotalMarginToAdd(UIElementCollection children, double marginBetweenChildren)
{
var visibleChildrenCount = children
.OfType<UIElement>()
.Count(x => x.Visibility != Visibility.Collapsed && GetFill(x) != StackPanelFill.Ignored);
var marginMultiplier = Math.Max(visibleChildrenCount - 1, 0);
var totalMarginToAdd = marginBetweenChildren * marginMultiplier;
return totalMarginToAdd;
}
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
"Orientation", typeof(Orientation), typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly DependencyProperty MarginBetweenChildrenProperty = DependencyProperty.Register(
"MarginBetweenChildren", typeof(double), typeof(StackPanel),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public double MarginBetweenChildren
{
get { return (double)GetValue(MarginBetweenChildrenProperty); }
set { SetValue(MarginBetweenChildrenProperty, value); }
}
public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached(
"Fill", typeof(StackPanelFill), typeof(StackPanel),
new FrameworkPropertyMetadata(
StackPanelFill.Auto,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public static void SetFill(DependencyObject element, StackPanelFill value)
{
element.SetValue(FillProperty, value);
}
public static StackPanelFill GetFill(DependencyObject element)
{
return (StackPanelFill)element.GetValue(FillProperty);
}
}
public enum StackPanelFill
{
Auto, Fill, Ignored
}
}

Strange act of canvas when Scale/ Zoom in Silverlight

I'm working on Zooming with Silverlight 5,
My Idea is to zoom according to mouse position on the Canvas, and the ability to Drag that Canvas,
The problem I have is when scale is less than 1, about 0.6 or 0.5, point to the corner of the canvas and wheel up, the canvas will change its position or "jump", any help please?
these two photos describe the status before Then after:
I have the following XAML:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer x:Name="sv" Margin="0,0,0,76" ScrollViewer.VerticalScrollBarVisibility="Disabled" Background="#FFE3E7F1">
<Canvas x:Name="grd" Height="394" Width="630">
<Canvas x:Name="cvs" Background="White" MouseWheel="cvs_MouseWheel" MouseLeftButtonDown="cvs_MouseLeftButtonDown" MouseLeftButtonUp="cvs_MouseLeftButtonUp" MouseMove="cvs_MouseMove" Height="391" Canvas.Left="2" Canvas.Top="1" Width="625">
<Canvas.Effect>
<DropShadowEffect ShadowDepth="0"/>
</Canvas.Effect>
<Rectangle Height="70" Canvas.Left="155" Canvas.Top="58" Width="79" Fill="#FFFFBFBF"/>
<Rectangle Height="70" Canvas.Left="544" Canvas.Top="126" Width="79" Fill="#FF8B92FF"/>
</Canvas>
</Canvas>
</ScrollViewer>
</Grid>
and here's the C#:
public partial class MainPage : UserControl
{
CompositeTransform canvasTransform = new CompositeTransform();
bool canDragCanvas;
double mouseRelatedPositionX = 0;
double mouseRelatedPositionY = 0;
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
private void cvs_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var scaleFactor = 0.2*(e.Delta < 0?-1:1);
var centerX = e.GetPosition(cvs).X;
var centerY = e.GetPosition(cvs).Y;
if (centerX > cvs.ActualWidth * canvasTransform.ScaleX || centerX < 0 || centerY > cvs.ActualHeight * canvasTransform.ScaleY || centerY < 0)
{
centerX = cvs.ActualWidth/2;
centerY = cvs.ActualHeight/2;
}
canvasTransform.CenterX = centerX;
canvasTransform.CenterY = centerY;
canvasTransform.ScaleX += scaleFactor;
canvasTransform.ScaleY += scaleFactor;
cvs.RenderTransform = canvasTransform;
}
private void cvs_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = true;
mouseRelatedPositionX = e.GetPosition(cvs).X;
mouseRelatedPositionY = e.GetPosition(cvs).Y;
}
private void cvs_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
canDragCanvas = false;
}
private void cvs_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if(!canDragCanvas) return;
var leftValueToAdd = e.GetPosition(cvs).X - mouseRelatedPositionX;
var topValueToAdd = e.GetPosition(cvs).Y - mouseRelatedPositionY;
UpdateCanvasPosition(leftValueToAdd*canvasTransform.ScaleX, topValueToAdd*canvasTransform.ScaleX);
}
void UpdateCanvasPosition(double leftValueToAdd,double topValueToAdd)
{
var leftOffset = canvasTransform.CenterX - canvasTransform.CenterX * canvasTransform.ScaleX;
var rightOffset = (cvs.ActualWidth - canvasTransform.CenterX) - (cvs.ActualWidth - canvasTransform.CenterX) * canvasTransform.ScaleX;
var topOffset = canvasTransform.CenterY - canvasTransform.CenterY * canvasTransform.ScaleY;
var bottomOffset = (cvs.ActualHeight - canvasTransform.CenterY) - (cvs.ActualHeight - canvasTransform.CenterY) * canvasTransform.ScaleY;
var canvasLeftInBorders = Canvas.GetLeft(cvs)+ leftValueToAdd + leftOffset > 0;
var canvasRightInBorders = Canvas.GetLeft(cvs) + cvs.ActualWidth * canvasTransform.ScaleX + leftValueToAdd + leftOffset < grd.ActualWidth;
var canvasTopInBorders = Canvas.GetTop(cvs) + topValueToAdd + topOffset > 0;
var canvasBottomInBorders = Canvas.GetTop(cvs) + cvs.ActualHeight * canvasTransform.ScaleY + topValueToAdd + topOffset < grd.ActualHeight;
if (leftValueToAdd > 0)
{
if (canvasLeftInBorders)
leftValueToAdd = 0;
}
else if (leftValueToAdd < 0)
if (canvasRightInBorders)
leftValueToAdd = 0;
if (topValueToAdd > 0)
{
if (canvasTopInBorders)
topValueToAdd = 0;
}
else if (topValueToAdd < 0)
if (canvasBottomInBorders)
topValueToAdd = 0;
Canvas.SetLeft(cvs, Canvas.GetLeft(cvs) + leftValueToAdd);
Canvas.SetTop(cvs,Canvas.GetTop(cvs)+topValueToAdd);
}
}
Basically you are attaching mouse events to the surface that is zooming and the mouse coordinates get altered too. Silverlight is designed to still be interactive when you rotate, zoom and tilt.
You want to put a transparent layer over the top, that is not zoomed, and attached your mouse methods that that layer.
If you leave that layer turned on you will have to calculate collisions, but you can make it so that the layer only appears on mouse down and goes away on mouse up.

Silverlight cannot update property after animation runs

Im trying to create a simple pan and zoom app using silverlight 4, but Im having trouble updating the TranslateTransform and ScaleTransform properties after I run an animation on them.
I have tried to set the FillBehaviour to Stop, with no success.
Here is the code that I have:
<Canvas x:Name="LayoutRoot" Background="White" Width="800" Height="600">
<Canvas x:Name="PanningCanvas" Height="600" Width="800">
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="CanvasScale"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform x:Name="CanvasTransform"/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle x:Name="MovingRect" Fill="#FF834040" Height="209" Canvas.Left="219" Stroke="#FF500505" Canvas.Top="220" Width="329" StrokeThickness="2"/>
</Canvas>
<Button Content="Animate" Width="107" Canvas.Left="38" Canvas.Top="46" ClickMode="Press" Click="Button_Click"/>
</Canvas>
And here is the C# behind it all (slightly abbreviated)
public partial class MainPage : UserControl
{
private ManipulationProcessor2D manipulationProcessor;
public MainPage()
{
InitializeComponent();
this.manipulationProcessor = new ManipulationProcessor2D(Manipulations2D.Translate | Manipulations2D.Scale);
this.manipulationProcessor.Delta += OnManipulationDelta;
Touch.FrameReported += OnCapturedTouchReported;
}
private void OnManipulationDelta(object sender, Manipulation2DDeltaEventArgs e)
{
float zoomVal = e.Delta.ScaleX;
Point pinchPoint = new Point(e.OriginX, e.OriginY);
float xTranslation = e.Delta.TranslationX;
float yTranslation = e.Delta.TranslationY;
if (zoomVal != 1.0)
{
Zoom(zoomVal, PanningCanvas.RenderTransform.Inverse.Transform(pinchPoint), pinchPoint, xTranslation, yTranslation, true);
}
else if (xTranslation != 0 || yTranslation != 0)
{
Translate(xTranslation, yTranslation);
}
}
public void Zoom(double zoom, Point pinchPosition, Point physicalPosition, float xTranslation, float yTranslation, bool isFinger)
{
if (isFinger)
{
CanvasScale.ScaleX = CanvasScale.ScaleX * zoom;
CanvasScale.ScaleY = CanvasScale.ScaleY * zoom;
CanvasTransform.X = -1 * (pinchPosition.X * CanvasScale.ScaleX - physicalPosition.X);
CanvasTransform.Y = -1 * (pinchPosition.Y * CanvasScale.ScaleY - physicalPosition.Y);
}
else
{
CanvasScale.ScaleX = CanvasScale.ScaleX + zoom;
CanvasScale.ScaleY = CanvasScale.ScaleY + zoom;
}
}
private void Translate(float xTranslation, float yTranslation)
{
CanvasTransform.X += xTranslation;
CanvasTransform.Y += yTranslation;
}
private void OnCapturedTouchReported(object sender, TouchFrameEventArgs e)
{
//..removed..//
// process manipulations
this.manipulationProcessor.ProcessManipulators(DateTime.UtcNow.Ticks,manipulators);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AnimatePosition(new Point(400, 300), 1);
AnimateScale(1.2, 1);
}
private Storyboard translateStoryboard = new Storyboard();
private Storyboard scaleStoryboard = new Storyboard();
public void AnimatePosition(Point destinationPoint, double lengthOfAnimation)
{
Point offset = new Point(destinationPoint.X, destinationPoint.Y);
var translationAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.X };
translateStoryboard.Children.Add(translationAnimationX);
Storyboard.SetTargetProperty(translationAnimationX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"));
var translationAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.Y };
translateStoryboard.Children.Add(translationAnimationY);
Storyboard.SetTargetProperty(translationAnimationY, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"));
Storyboard.SetTarget(translateStoryboard, PanningCanvas);
translateStoryboard.Begin();
}
public void AnimateScale( double scale, double lengthOfAnimation)
{
var scaleAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = scale };
scaleStoryboard.Children.Add(scaleAnimationX);
Storyboard.SetTargetProperty(scaleAnimationX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
var scaleAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = scale };
scaleStoryboard.Children.Add(scaleAnimationY);
Storyboard.SetTargetProperty(scaleAnimationY, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
Storyboard.SetTarget(scaleStoryboard, PanningCanvas);
scaleStoryboard.Begin();
}
}
The issue is that after I click the button, I cannot translate or zoom anymore...
please help!
Mark
Ok, so I got it working, but I dont know why.
Changing the animation to work like this:
Storyboard translateStoryboard = new Storyboard();
Point offset = new Point(destinationPoint.X, destinationPoint.Y);
var translationAnimationX = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.X };
var translationAnimationY = new DoubleAnimation() { SpeedRatio = 1, Duration = new Duration(TimeSpan.FromSeconds(lengthOfAnimation)), To = offset.Y };
Storyboard.SetTargetProperty(translationAnimationX, new PropertyPath("X"));
Storyboard.SetTargetProperty(translationAnimationY, new PropertyPath("Y"));
Storyboard.SetTargetName(translationAnimationX, "CanvasTransform");
Storyboard.SetTargetName(translationAnimationY, "CanvasTransform");
translateStoryboard.Children.Add(translationAnimationX);
translateStoryboard.Children.Add(translationAnimationY);
translateStoryboard.Begin();
Did the trick, but I dont know why...
Can someone please explain it to me?

In a WPF Grid, how can I find the Row & Column at the mouse location?

I have a WPF Grid with some rows and columns, e.g.
<Grid Name="myGrid" MouseMove="OnMouseMove">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
With a handler for MouseMove in the .cs file, e.g.
private void OnMouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(myGrid);
// What row & col is the mouse over?
}
I want to be able to find which row and column in the grid the mouse is over, is this possible?
[Note: this is a simplified version of the problem, so it looks a little odd to present it in this way - it's part of some drag & drop between grids functionality]
I hope you have already find a solution. I faced to the same problem and I found this unanswered post. So I think the next one will be happy to find this solution :
private void OnMouseMove(object sender, MouseEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
This the following can be done (but shouldn't be used on larger Grids). It is only for Rows, but a second loop can be applied to the columns...
The answer of Nicolas only works if the grid contains some UIElements (whose position in the grid are determined - I guess that would also cause trouble if an element spans several rows/columns).
private void ItemsGrid_MouseMove(object sender, MouseEventArgs e)
{
double y = e.GetPosition(ItemsGrid).Y;
double start = 0.0;
int row = 0;
foreach(RowDefinition rd in ItemsGrid.RowDefinitions)
{
start += rd.ActualHeight;
if (y < start)
{
break;
}
row++;
}
System.Diagnostics.Debug.WriteLine("Row : " + row);
}
public static class GridExtentions
{
public static T Parent<T>(this DependencyObject root) where T : class
{
if (root is T) { return root as T; }
DependencyObject parent = VisualTreeHelper.GetParent(root);
return parent != null ? parent.Parent<T>() : null;
}
public static Point GetColumnRow(this Grid obj, Point relativePoint) { return new Point(GetColumn(obj, relativePoint.X), GetRow(obj, relativePoint.Y)); }
private static int GetRow(Grid obj, double relativeY) { return GetData(obj.RowDefinitions, relativeY); }
private static int GetColumn(Grid obj, double relativeX) { return GetData(obj.ColumnDefinitions, relativeX); }
private static int GetData<T>(IEnumerable<T> list, double value) where T : DefinitionBase
{
var start = 0.0;
var result = 0;
var property = typeof(T).GetProperties().FirstOrDefault(p => p.Name.StartsWith("Actual"));
if (property == null) { return result; }
foreach (var definition in list)
{
start += (double)property.GetValue(definition);
if (value < start) { break; }
result++;
}
return result;
}
}
Usage:
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (hit == null) { return; }
var grid = hit.VisualHit.Parent<Grid>();
if (grid == null) { return; }
var gridPosition = grid.GetColumnRow(e.GetPosition(grid));
MessageBox.Show(string.Format("Grid location Row: {1} Column: {0}", gridPosition.X, gridPosition.Y));
}
replace Grid.ColumnDefinitions with reference to Grid component
int GetColumn(double point)
{
int index = 0;
foreach(var column in Grid.ColumnDefinitions)
{
if(point > column.Offset && point < (column.Offset + column.ActualWidth))
return index;
index++;
}
return 0;
}
Hope can help. it's working well for my app
public class GridCell
{
public int GridRow { get; set; }
public int GridCol { get; set; }
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
Point point = e.GetPosition(this.myGrid);
Grid gridTarget = GetLastedGridContainPoint(point, this.myGrid);
Point pointTarget = this.myGrid.TranslatePoint(point, gridTarget);
GridCell cell = GetGridCellContainPoint(pointTarget, gridTarget);
}
private bool IsPointInGrid(Point relativePoint, Grid grid)
{
if (relativePoint.X < 0 || relativePoint.X > grid.ActualWidth ||
relativePoint.Y < 0 || relativePoint.Y > grid.ActualHeight)
{
return false;
}
return true;
}
private Grid GetLastedGridContainPoint(Point relativePoint, Grid gridParent)
{
Grid gridReturn = null;
if (gridParent.Children.Count > 0)
{
Point relativeChildPoint;
foreach (UIElement e in gridParent.Children)
{
if (e is Grid)
{
relativeChildPoint = gridParent.TranslatePoint(relativePoint, (e as Grid));
gridReturn = GetLastedGridContainPoint(relativeChildPoint, (e as Grid));
if (gridReturn != null)
{
break;
}
}
}
}
if (gridReturn == null)
{
if (IsPointInGrid(relativePoint, gridParent))
{
gridReturn = gridParent;
}
}
return gridReturn;
}
private GridCell GetGridCellContainPoint(Point relativePoint, Grid gridTarget)
{
if (!IsPointInGrid(relativePoint, gridTarget))
{
return null;
}
GridCell cell = new GridCell();
double dbStart = 0;
double dbEnd = 0;
if (gridTarget.ColumnDefinitions.Count > 0)
{
for (int i = 0; i < gridTarget.ColumnDefinitions.Count; i++)
{
dbStart = dbEnd;
dbEnd += gridTarget.ColumnDefinitions[i].ActualWidth;
if (relativePoint.X >= dbStart && relativePoint.X < dbEnd)
{
cell.GridCol = i;
break;
}
}
}
dbStart = 0;
dbEnd = 0;
if (gridTarget.RowDefinitions.Count > 0)
{
for (int i = 0; i < gridTarget.RowDefinitions.Count; i++)
{
dbStart = dbEnd;
dbEnd += gridTarget.RowDefinitions[i].ActualHeight;
if (relativePoint.Y >= dbStart && relativePoint.Y < dbEnd)
{
cell.GridRow = i;
break;
}
}
}
return cell;
}
I had exactly your same problem and I am also using FluidKit. I'm trying to build a form designer where you could drag a control from a Toolbox and drop it into a Grid cell. Here is how I solved it:
I created a grid with two dummy Rectangles in the first row:
<Grid Name="myCanvas" ShowGridLines="True" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor1}"
Grid.Row="0" Grid.Column="0" Width="200" Height="100" Fill="Blue" Stroke="Black" StrokeThickness="4" />
<Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor2}"
Grid.Row="0" Grid.Column="1" Width="200" Height="100" Fill="Red" Stroke="Black" StrokeThickness="4" />
</Grid>
It looks as follows:
Notice that I defined a DropTargetAdvisor for each of my rectangles, now the logic is as follows:
You drag/drop a Control from the Toolbox into a Cell
The OnDropCompleted method in the DropTargetAdvisor will then remove the rectangle where you're dropping the control and will get its coordinates from the Grid.Row and Grid.Column Attached properties.
Once you have the coordinates you can set those Attached properties to your dragged control and add it to your grid.
Here is my DefaultDropTargetAdvisor.OnDropCompleted method:
public void OnDropCompleted(IDataObject obj, Point dropPoint)
{
UIElement dragged_control = ExtractElement(obj);
//Get the current Rectangle
FrameworkElement fe = TargetUI as FrameworkElement;
//Get parent Grid from this Rectangle
Grid g = fe.Parent as Grid;
//Get row and columns of this Rectangle
int row = Grid.GetRow(TargetUI);
int col = Grid.GetColumn(TargetUI);
//Remove Rectangle
g.Children.Remove(TargetUI);
//Set row and column for the dragged control
Grid.SetRow(dragged_control, row);
Grid.SetColumn(dragged_control, col);
//Add dragged control to Grid in that row/col
g.Children.Add(dragged_control);
}
For a Telerik RadGridView, the best approach if the grid doesn't contain UI elements is to use ChildrenOfType<> in a Linq expression with IsMouseOver.
private void myGridView_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
MyCustomClass myClass = null;
var rows = this.myGridView.ChildrenOfType<GridViewRow>().Where(r => r.IsMouseOver == true);
foreach (var row in rows)
{
if (row is GridViewNewRow) break;
GridViewRow gvr = (GridViewRow)row;
myClass = (MyCustomClass)gvr.Item;
}
// do something with myClass here if we have found a row under mouse
}
Andre's answer has a minor mistake, as the coordinates obtained do not account for the row and column headers in the DataGrid. At least this was the case when I implemented the solution in Visual Basic.
You can also modify the examples shown to account for a large DataGrid. It seems to me that the limitation there is based on the scrolled view, so I show two implementations of this correction:
Private Sub myGrid_MouseMove(sender As Object, e As MouseEventArgs) Handles myGrid.MouseMove
Dim total As Double
Dim myScrollViewer As ScrollViewer = FindVisualChild(Of ScrollViewer)(myGrid)
Dim cursorPositionX = e.GetPosition(myGrid).X
Dim columnIndex As Integer = -1
total = 0
'Horizontal offset'
Dim rowHeaders As DataGridRowHeader = FindVisualChild(Of DataGridRowHeader)(myGrid)
cursorPositionX -= (rowHeaders.ActualWidth - myScrollViewer.HorizontalOffset)
For Each column As DataGridColumn In myGrid.Columns
If cursorPositionX < total Then Exit For
columnIndex += 1
total += column.Width.DisplayValue
Next
Dim cursorPositionY = e.GetPosition(myGrid).Y
Dim rowIndex As Integer = -1
total = 0
'Vertical offset'
Dim originalOffset As Double = myScrollViewer.VerticalOffset
Dim colHeadersPresenter As DataGridColumnHeadersPresenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(myGrid)
cursorPositionY -= colHeadersPresenter.ActualHeight
For Each row As System.Data.DataRowView In myGrid.Items
If cursorPositionY < total Then Exit For
rowIndex += 1
Dim dgRow As DataGridRow = GetRowByIndex(myGrid, rowIndex)
total += dgRow.ActualHeight
'GetRowByIndex will scroll the view to bring the DataGridRow of interest into view, which throws off the counter. This adjusts for that'
myGrid.UpdateLayout()
If Not myScrollViewer.VerticalOffset = originalOffset Then myGrid.ScrollIntoView(myGrid.Items(CInt(myScrollViewer.ViewportHeight + originalOffset - 1)))
myGrid.UpdateLayout()
If myScrollViewer.VerticalOffset > rowIndex Then cursorPositionY += dgRow.ActualHeight
Next
End Sub
Note that the ScrollViewer.HorizontalOffset Property returns a value in Device Independent Pixels, so I merely offset my location once before looping through the columns.
Note that the ScrollViewer.VerticalOffset Property returns the number of items if CanContentScroll = True. So in my example, on each loop I offset the counter by the height of one item (the DataGridRow). If CanContentScroll = False, then it can be handled as in the case of the column index loop.
I couldn't find row definitions in the DataGrid for .Net 4.0 in Visual Basic, but the following supporting function helps for obtaining the DataGridRow:
Function GetRowByIndex(ByVal p_dataGrid As DataGrid,
ByVal p_index As Integer) As DataGridRow
Dim row As DataGridRow
row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)
If IsNothing(row) Then
'May be virtualized, bring into view and try again.'
p_dataGrid.UpdateLayout()
p_dataGrid.ScrollIntoView(p_dataGrid.Items(p_index))
row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)
End If
Return row
End Function
And the FindVisualChild function in Visual Basic:
Function FindVisualChild(Of childItem As DependencyObject)(ByVal p_obj As DependencyObject) As childItem
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(p_obj) - 1
Dim child As DependencyObject = VisualTreeHelper.GetChild(p_obj, i)
If child IsNot Nothing AndAlso TypeOf child Is childItem Then
Return CType(child, childItem)
Else
Dim childOfChild As childItem = FindVisualChild(Of childItem)(child)
If childOfChild IsNot Nothing Then
Return childOfChild
End If
End If
Next i
Return Nothing
End Function

Resources