I'm currently working on a kind of VirtualizedWrapPanel to use as the ItemsPanel in a ListView.
After following this guy's instructions, and borrowing heavily from this guy's implementation found on codeproject but I don't have the reputation to post the link so sorry..., I have something that is nicely shaping up to be exactly what I need.
The item size is fixed so the scrolling is pixel based. the orientation is always horizontal.
the ListView :
<ListView Name="lv"
ItemsSource="{Binding CV}"
IsSynchronizedWithCurrentItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<i:Interaction.Behaviors>
<local:ScrollToSelectionListViewBehavior/> <!-- Behavior calling ScrollIntoView whenever the selection changes -->
<local:ListViewSelectedItemsBehavior SelectedItems="{Binding SelectedItems}"/> <!-- Behavior exposing the attached ListView's SelectedItems array -->
</i:Interaction.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Height="100" Width="100" Orientation="Vertical">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Width="90" Height="90">
<TextBlock Text ="{Binding ItemText}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<local:VirtualizingWrapPanel ItemHeight="100" ItemWidth="110" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
the local:VirtualizingWrapPanel :
public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
{
private ScrollViewer _owner;
private const bool _canHScroll = false;
private bool _canVScroll = false;
private Size _extent = new Size(0, 0);
private Size _viewport = new Size(0, 0);
private Point _offset;
UIElementCollection _children;
ItemsControl _itemsControl;
IItemContainerGenerator _generator;
Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
#region Properties
private Size ChildSlotSize
{
get { return new Size(ItemWidth, ItemHeight); }
}
#endregion
#region Dependency Properties
[TypeConverter(typeof(LengthConverter))]
public double ItemHeight
{
get { return (double)base.GetValue(ItemHeightProperty); }
set { base.SetValue(ItemHeightProperty, value); }
}
[TypeConverter(typeof(LengthConverter))]
public double ItemWidth
{
get { return (double)base.GetValue(ItemWidthProperty); }
set { base.SetValue(ItemWidthProperty, value); }
}
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
private int LineCapacity
{ get { return Math.Max((_viewport.Width != 0) ? (int)(_viewport.Width / ItemWidth) : 0, 1); } }
private int LinesCount
{ get { return (ItemsCount > 0) ? ItemsCount / LineCapacity : 0 ; } }
private int ItemsCount
{ get { return _itemsControl.Items.Count; } }
public int FirstVisibleLine
{ get { return (int)(_offset.Y / ItemHeight); } }
public int FirstVisibleItemVPos
{ get { return (int)((FirstVisibleLine * ItemHeight) - _offset.Y); } }
public int FirstVisibleIndex
{ get { return (FirstVisibleLine * LineCapacity); } }
#endregion
#region VirtualizingPanel overrides
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
_itemsControl = ItemsControl.GetItemsOwner(this);
_children = InternalChildren;
_generator = ItemContainerGenerator;
this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
}
protected override Size MeasureOverride(Size availableSize)
{
if (_itemsControl == null || _itemsControl.Items.Count == 0)
return availableSize;
if (availableSize != _viewport)
{
_viewport = availableSize;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
Size childSize = new Size(ItemWidth, ItemHeight);
Size extent = new Size(availableSize.Width, LinesCount * ItemHeight);
if (extent != _extent)
{
_extent = extent;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
foreach (UIElement child in this.InternalChildren)
{
child.Measure(childSize);
}
_realizedChildLayout.Clear();
Size realizedFrameSize = availableSize;
int firstVisibleIndex = FirstVisibleIndex;
GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);
int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
int current = firstVisibleIndex;
using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
{
bool stop = false;
double currentX = 0;
double currentY = FirstVisibleItemVPos;
while (current < ItemsCount)
{
bool newlyRealized;
// Get or create the child
UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
if (newlyRealized)
{
// Figure out if we need to insert the child at the end or somewhere in the middle
if (childIndex >= _children.Count)
{
base.AddInternalChild(child);
}
else
{
base.InsertInternalChild(childIndex, child);
}
_generator.PrepareItemContainer(child);
child.Measure(ChildSlotSize);
}
else
{
// The child has already been created, let's be sure it's in the right spot
Debug.Assert(child == _children[childIndex], "Wrong child was generated");
}
childSize = child.DesiredSize;
Rect childRect = new Rect(new Point(currentX, currentY), childSize);
if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
{
currentY = currentY + ItemHeight;
currentX = 0;
childRect.X = currentX;
childRect.Y = currentY;
}
if (currentY > realizedFrameSize.Height)
stop = true;
currentX = childRect.Right;
_realizedChildLayout.Add(child, childRect);
if (stop)
break;
current++;
childIndex++;
}
}
CleanUpItems(firstVisibleIndex, current - 1);
return availableSize;
}
public void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
for (int i = _children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
//var c = _children[i] as ListViewItem;
//if(c!= null && c.IsSelected)
//{
//}
_generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if (finalSize != _viewport)
{
_viewport = finalSize;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
Size childSize = new Size(ItemWidth, ItemHeight);
Size extent = new Size(finalSize.Width, LinesCount * ItemHeight);
if (extent != _extent)
{
_extent = extent;
if (_owner != null)
_owner.InvalidateScrollInfo();
}
if (_children != null)
{
foreach (UIElement child in _children)
{
var layoutInfo = _realizedChildLayout[child];
child.Arrange(layoutInfo);
}
}
return finalSize;
}
protected override void BringIndexIntoView(int index)
{
SetVerticalOffset((index / LineCapacity) * ItemHeight);
}
protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
{
base.OnItemsChanged(sender, args);
_offset.X = 0;
_offset.Y = 0;
switch (args.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
break;
}
}
#endregion
#region EventHandlers
public void Resizing(object sender, EventArgs e)
{
var args = e as SizeChangedEventArgs;
if(args.WidthChanged)
{
int lineCapacity = LineCapacity;
int previousLineCapacity = (int)(args.PreviousSize.Width / ItemWidth);
if (previousLineCapacity != lineCapacity)
{
int previousFirstItem = ((int)(_offset.Y / ItemHeight) <= 0) ? 0 : ((int)(_offset.Y / ItemHeight) * previousLineCapacity);
BringIndexIntoView(previousFirstItem);
}
}
if (_viewport.Width != 0)
{
MeasureOverride(_viewport);
}
}
#endregion
#region IScrollInfo Implementation
public ScrollViewer ScrollOwner
{
get { return _owner; }
set { _owner = value; }
}
public bool CanHorizontallyScroll
{
get { return false; }
set { if (value == true) throw (new ArgumentException("VirtualizingWrapPanel does not support Horizontal scrolling")); }
}
public bool CanVerticallyScroll
{
get { return _canVScroll; }
set { _canVScroll = value; }
}
public double ExtentHeight
{
get { return _extent.Height;}
}
public double ExtentWidth
{
get { return _extent.Width; }
}
public double HorizontalOffset
{
get { return _offset.X; }
}
public double VerticalOffset
{
get { return _offset.Y; }
}
public double ViewportHeight
{
get { return _viewport.Height; }
}
public double ViewportWidth
{
get { return _viewport.Width; }
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
var element = (UIElement)visual;
int itemIndex = gen.IndexFromContainer(element);
while (itemIndex == -1)
{
element = (UIElement)VisualTreeHelper.GetParent(element);
itemIndex = gen.IndexFromContainer(element);
}
Rect elementRect = _realizedChildLayout[element];
if (elementRect.Bottom > ViewportHeight)
{
double translation = elementRect.Bottom - ViewportHeight;
_offset.Y += translation;
}
else if (elementRect.Top < 0)
{
double translation = elementRect.Top;
_offset.Y += translation;
}
InvalidateMeasure();
return elementRect;
}
public void LineDown()
{
SetVerticalOffset(VerticalOffset + 50);
}
public void LineUp()
{
SetVerticalOffset(VerticalOffset - 50);
}
public void MouseWheelDown()
{
SetVerticalOffset(VerticalOffset + 50);
}
public void MouseWheelUp()
{
SetVerticalOffset(VerticalOffset - 50);
}
public void PageDown()
{
int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
SetVerticalOffset(VerticalOffset + (fullyVisibleLines * ItemHeight));
}
public void PageUp()
{
int fullyVisibleLines = (int)(_viewport.Height / ItemHeight);
SetVerticalOffset(VerticalOffset - (fullyVisibleLines * ItemHeight));
}
public void SetVerticalOffset(double offset)
{
if (offset < 0 || _viewport.Height >= _extent.Height)
{
offset = 0;
}
else
{
if (offset + _viewport.Height >= _extent.Height)
{
offset = _extent.Height - _viewport.Height;
}
}
_offset.Y = offset;
if (_owner != null)
_owner.InvalidateScrollInfo();
InvalidateMeasure();
}
public void LineLeft() { throw new NotImplementedException(); }
public void LineRight() { throw new NotImplementedException(); }
public void MouseWheelLeft() { throw new NotImplementedException(); }
public void MouseWheelRight() { throw new NotImplementedException(); }
public void PageLeft() { throw new NotImplementedException(); }
public void PageRight() { throw new NotImplementedException(); }
public void SetHorizontalOffset(double offset) { throw new NotImplementedException(); }
#endregion
#region methods
#endregion
}
Now my problem is : An Item Selection should always Deselect the previously selected item, when using a normal WrapPanel, the previously selected ListViewItem's IsSelected property is always set to false before the new selected ListViewItem's IsSelected is set to true.
This deselection does not happen with my VirtualizingPanel when the previously selected item is no longer realized (when it is not visible in the viewport), so I end up with two or more selected items at once and the panel's behavior becomes really weird. Sometimes it even settles into an infinite loop, the two selected items yanking visibility from each other in a never ending battle.
I searched a bit for a solution to this problem but I don't really know where to start or what to search for.
Here is a test project if you want to experiment with it.
Thanks
I found a way by preventing the virtualization of selected items. Now the control behaves correctly.
public void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
for (int i = _children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
//I don't much like this cast
// how do I access the IsSelectedProperty
// of a UIElement of any type ( ListBoxItem, TreeViewItem...)?
var c = _children[i] as ListViewItem;
c.IsEnabled = false;
if (c != null && c.IsSelected)
{
var layoutInfo = new Rect(0, 0, 0, 0);
c.Arrange(layoutInfo);
}
else
{
_generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
}
In ArrangeOverride :
if (_children != null)
{
foreach (UIElement child in _children)
{
if (child.IsEnabled)
{
var layoutInfo = _realizedChildLayout[child];
child.Arrange(layoutInfo);
}
}
}
In MeasureOverride:
while (current < ItemsCount)
{
bool newlyRealized;
// Get or create the child
UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
child.IsEnabled = true;
I'm still curious to know if there's a better way. In the meantime this will do.
btw I'll update the test project with this fix in case anyone wants a virtualizing wrappanel.
I have been trying to make a Ellipse to follow a given trajectory with the DoubleAnimation class. So far not too good.
I made a custom class AnimationQueue :
private class AnimationQueue
{
private List<DoubleAnimation> animation1 ;
private DependencyProperty property1;
private bool running1;
private List<DoubleAnimation> animation2;
private DependencyProperty property2;
private bool running2;
private int curent;
private UIElement element;
public AnimationQueue(UIElement element , DependencyProperty property )
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property;
}
public AnimationQueue(UIElement element, DependencyProperty property1 , DependencyProperty property2)
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property1;
this.property2 = property2;
}
public void queueAnimation(DoubleAnimation animation1, DoubleAnimation animation2)
{
animation1.Completed += (s, e) =>
{
running1 = false;
};
animation2.Completed += (s, e) =>
{
running2 = false;
};
this.animation1.Add(animation1);
this.animation2.Add(animation2);
}
public void start(int i)
{
if (animation1.Count != animation2.Count)
Console.WriteLine("Animation Queue not equal");
else
{
if (i == animation1.Count)
Console.WriteLine("Animation finished");
else if (i <= animation1.Count && i <= animation2.Count)
{
element.BeginAnimation(property1, animation1[i]);
element.BeginAnimation(property2, animation2[i]);
if (running1 == false && running2 == false)
{
i++;
start(i);
}
}
}
}
public void start()
{
curent = 0;
element.BeginAnimation(property1, animation1[curent]);
element.BeginAnimation(property2, animation2[curent]);
if(running1 == false && running2 == false)
start(curent++);
}
}`
In this class I add 2 animations for each point in the coordinate list . 1 to animate the x axis movement and 1 to anymate the y axis movement . The adding goes like this :
public void StartCourse()
{
int i = 0;
List<Point> coordinates = course.firstFuntion();
while (coordinates.Count != i)
{
queue1.queueAnimation(new DoubleAnimation(coordinates[i].X, new Duration(TimeSpan.FromMilliseconds(500))),
new DoubleAnimation(coordinates[i].Y, new Duration(TimeSpan.FromMilliseconds(500)))
);
i++;
}
queue1.start();
}
My problem is , I believe , in the AnimationQueue.Start() method. The program stars the animation , however it only executes the first animation . After that it stops . I tried to make use of the animation.Completed Event Handler but to no success.
BeginAnimation starts animation asynchrously. I suppose this may help:
private class AnimationQueue
{
private List<DoubleAnimation> animation1 ;
private DependencyProperty property1;
private bool running1;
private List<DoubleAnimation> animation2;
private DependencyProperty property2;
private bool running2;
private int curent;
private UIElement element;
public AnimationQueue(UIElement element , DependencyProperty property )
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property;
}
public AnimationQueue(UIElement element, DependencyProperty property1 , DependencyProperty property2)
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property1;
this.property2 = property2;
}
public void queueAnimation(DoubleAnimation animation1, DoubleAnimation animation2)
{
this.animation1.Add(animation1);
this.animation2.Add(animation2);
animation1.Completed += (s, e) =>
{
if (this.animation1.Contains(animation1))
{
int index1 = this.animation1.IndexOf(animation1);
if (index1 + 1 < this.animation1.Count)
{
element.BeginAnimation(property1, animation1[index1 + 1]);
}
}
};
animation2.Completed += (s, e) =>
{
if (this.animation2.Contains(animation2))
{
int index2 = this.animation2.IndexOf(animation2);
if (index2 + 1 < this.animation2.Count)
{
element.BeginAnimation(property2, animation2[index2 + 1]);
}
}
};
}
public void start()
{
curent = 0;
element.BeginAnimation(property1, animation1[curent]);
element.BeginAnimation(property2, animation2[curent]);
}
}`
I have a kinect project in wpf and it uses skeleton stream that tracks the left and right hand of its users and allows me to hover over buttons.
I tried making a new form and just copying and pasting everything so i can create a new page but it didnt work, i think i may have to reference the methods used in the main page, but i am unsure.
I want to be able to use the skeleton stream alongside the hovering method in a new window
Any help would be appreciated - i apologize if this does not make sense i am a beginner
Code
public partial class MainWindow : Window
{
private KinectSensor _Kinect;
private WriteableBitmap _ColorImageBitmap;
private Int32Rect _ColorImageBitmapRect;
private int _ColorImageStride;
private Skeleton[] FrameSkeletons;
List<Button> buttons;
static Button selected;
float handX;
float handY;
public MainWindow()
{
InitializeComponent();
InitializeButtons();
kinectButton.Click += new RoutedEventHandler(kinectButton_Click);
this.Loaded += (s, e) => { DiscoverKinectSensor(); };
this.Unloaded += (s, e) => { this.Kinect = null; };
}
//initialize buttons to be checked
private void InitializeButtons()
{
buttons = new List<Button> { button1, button2, quitButton};
}
//raise event for Kinect sensor status changed
private void DiscoverKinectSensor()
{
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
this.Kinect = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected);
}
private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Connected:
if (this.Kinect == null)
{
this.Kinect = e.Sensor;
}
break;
case KinectStatus.Disconnected:
if (this.Kinect == e.Sensor)
{
this.Kinect = null;
this.Kinect = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected);
if (this.Kinect == null)
{
MessageBox.Show("Sensor Disconnected. Please reconnect to continue.");
}
}
break;
}
}
public KinectSensor Kinect
{
get { return this._Kinect; }
set
{
if (this._Kinect != value)
{
if (this._Kinect != null)
{
UninitializeKinectSensor(this._Kinect);
this._Kinect = null;
}
if (value != null && value.Status == KinectStatus.Connected)
{
this._Kinect = value;
InitializeKinectSensor(this._Kinect);
}
}
}
}
private void UninitializeKinectSensor(KinectSensor kinectSensor)
{
if (kinectSensor != null)
{
kinectSensor.Stop();
kinectSensor.ColorFrameReady -= Kinect_ColorFrameReady;
kinectSensor.SkeletonFrameReady -= Kinect_SkeletonFrameReady;
}
}
private void InitializeKinectSensor(KinectSensor kinectSensor)
{
if (kinectSensor != null)
{
ColorImageStream colorStream = kinectSensor.ColorStream;
colorStream.Enable();
this._ColorImageBitmap = new WriteableBitmap(colorStream.FrameWidth, colorStream.FrameHeight,
96, 96, PixelFormats.Bgr32, null);
this._ColorImageBitmapRect = new Int32Rect(0, 0, colorStream.FrameWidth, colorStream.FrameHeight);
this._ColorImageStride = colorStream.FrameWidth * colorStream.FrameBytesPerPixel;
videoStream.Source = this._ColorImageBitmap;
kinectSensor.SkeletonStream.Enable(new TransformSmoothParameters()
{
Correction = 0.5f,
JitterRadius = 0.05f,
MaxDeviationRadius = 0.04f,
Smoothing = 0.5f
});
kinectSensor.SkeletonFrameReady += Kinect_SkeletonFrameReady;
kinectSensor.ColorFrameReady += Kinect_ColorFrameReady;
kinectSensor.Start();
this.FrameSkeletons = new Skeleton[this.Kinect.SkeletonStream.FrameSkeletonArrayLength];
}
}
private void Kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
using (ColorImageFrame frame = e.OpenColorImageFrame())
{
if (frame != null)
{
byte[] pixelData = new byte[frame.PixelDataLength];
frame.CopyPixelDataTo(pixelData);
this._ColorImageBitmap.WritePixels(this._ColorImageBitmapRect, pixelData,
this._ColorImageStride, 0);
}
}
}
private void Kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
using (SkeletonFrame frame = e.OpenSkeletonFrame())
{
if (frame != null)
{
frame.CopySkeletonDataTo(this.FrameSkeletons);
Skeleton skeleton = GetPrimarySkeleton(this.FrameSkeletons);
if (skeleton == null)
{
kinectButton.Visibility = Visibility.Collapsed;
}
else
{
Joint primaryHand = GetPrimaryHand(skeleton);
TrackHand(primaryHand);
}
}
}
}
//track and display hand
private void TrackHand(Joint hand)
{
if (hand.TrackingState == JointTrackingState.NotTracked)
{
kinectButton.Visibility = System.Windows.Visibility.Collapsed;
}
else
{
kinectButton.Visibility = System.Windows.Visibility.Visible;
DepthImagePoint point = this.Kinect.MapSkeletonPointToDepth(hand.Position, DepthImageFormat.Resolution640x480Fps30);
handX = (int)((point.X * LayoutRoot.ActualWidth / this.Kinect.DepthStream.FrameWidth) -
(kinectButton.ActualWidth / 2.0));
handY = (int)((point.Y * LayoutRoot.ActualHeight / this.Kinect.DepthStream.FrameHeight) -
(kinectButton.ActualHeight / 2.0));
Canvas.SetLeft(kinectButton, handX);
Canvas.SetTop(kinectButton, handY);
if (isHandOver(kinectButton, buttons)) kinectButton.Hovering();
else kinectButton.Release();
if (hand.JointType == JointType.HandRight)
{
kinectButton.ImageSource = "/Images/RightHand.png";
kinectButton.ActiveImageSource = "/Images/RightHand.png";
}
else
{
kinectButton.ImageSource = "/Images/LeftHand.png";
kinectButton.ActiveImageSource = "/Images/LeftHand.png";
}
}
}
//detect if hand is overlapping over any button
private bool isHandOver(FrameworkElement hand, List<Button> buttonslist)
{
var handTopLeft = new Point(Canvas.GetLeft(hand), Canvas.GetTop(hand));
var handX = handTopLeft.X + hand.ActualWidth / 2;
var handY = handTopLeft.Y + hand.ActualHeight / 2;
foreach (Button target in buttonslist)
{
Point targetTopLeft = new Point(Canvas.GetLeft(target), Canvas.GetTop(target));
if (handX > targetTopLeft.X &&
handX < targetTopLeft.X + target.Width &&
handY > targetTopLeft.Y &&
handY < targetTopLeft.Y + target.Height)
{
selected = target;
return true;
}
}
return false;
}
//get the hand closest to the Kinect sensor
private static Joint GetPrimaryHand(Skeleton skeleton)
{
Joint primaryHand = new Joint();
if (skeleton != null)
{
primaryHand = skeleton.Joints[JointType.HandLeft];
Joint rightHand = skeleton.Joints[JointType.HandRight];
if (rightHand.TrackingState != JointTrackingState.NotTracked)
{
if (primaryHand.TrackingState == JointTrackingState.NotTracked)
{
primaryHand = rightHand;
}
else
{
if (primaryHand.Position.Z > rightHand.Position.Z)
{
primaryHand = rightHand;
}
}
}
}
return primaryHand;
}
//get the skeleton closest to the Kinect sensor
private static Skeleton GetPrimarySkeleton(Skeleton[] skeletons)
{
Skeleton skeleton = null;
if (skeletons != null)
{
for (int i = 0; i < skeletons.Length; i++)
{
if (skeletons[i].TrackingState == SkeletonTrackingState.Tracked)
{
if (skeleton == null)
{
skeleton = skeletons[i];
}
else
{
if (skeleton.Position.Z > skeletons[i].Position.Z)
{
skeleton = skeletons[i];
}
}
}
}
}
return skeleton;
}
void kinectButton_Click(object sender, RoutedEventArgs e)
{
selected.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, selected));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
message.Content = "Button 1 clicked!";
}
private void button2_Click(object sender, RoutedEventArgs e)
{
message.Content = "Button 2 clicked!";
}
private void quitButton_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
You can do this in a couple of different ways, and more ways then what is below.
You could pass a reference to the sensor itself to the new window when it is created:
public MainWindow()
{
// init code for window and Kinect
// show the second window
SecondWindow mySecondWindow = new SecondWindow(_Kinect);
mySecondWindow.Show();
// other stuff...
}
public class SecondWindow : Window
{
public SecondWindow(KinectSensor sensor)
{
// ... stuff
sensor.SkeletonFrameReady += SkeletonFrameReadyCallback;
// ... more stuff
}
}
Then subscribe to the SkeletonFrameReady callback in your second window. This might work for your situation if you are interacting with items in the seconds window.
Another way would be to create a public callback inside your second window and subscribe it to the SkeletonFrameReady event.
public MainWindow()
{
// init code for window and Kinect
// show the second window
SecondWindow mySecondWindow = new SecondWindow(_Kinect);
mySecondWindow.Show();
_Kinect.SkeletonFrameReady += mySecondWindow.SkeletonFrameReadyCallback;
}
I also notice in your code that you are firing events. If you are wanting to act on events from one window in a different window, you can subscribe to those custom events in the same mentioned above.
I am using RadTileView in my project, and by default the Tile Drag and Drop is enabled when they are in restored state
,
but I can't achieve the same functionality when 1 tile is in maximized state and all others are in minimized state
,
I think that Telerik hasn't provided this functionality in their RadTileView control. What would be the best way to achieve this, or is it possible or not?
After searching through different blogs, I came to know that this functionality is not available in Telerik Tile view out of the box, but they have added it in their wish list, You can vote for this feature here http://www.telerik.com/support/pits.aspx#/public/silverlight/2449
However as a work arround I have implemented a Behavior my self for RadTileView, which will do the required task.
public class RadTilesDragDropBehavior : Behavior<RadTileView>
{
private RadTileViewItem draggingTile { get; set; }
public TileViewDragDropBehavior()
{
// Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
DragDropManager.AddDragInitializeHandler(AssociatedObject, OnDragInitialize);
DragDropManager.AddDragDropCompletedHandler(AssociatedObject, OnDragAndDropCompleted);
AssociatedObject.PreviewDragOver += MyTileView_PreviewDragOver;
}
private void OnDragInitialize(object sender, DragInitializeEventArgs args)
{
var tileView = sender as RadTileView;
var tileViewItem = args.OriginalSource as RadTileViewItem;
Point pt = Util.CorrectGetPosition((RadTileView)sender);
HitTestResult result = VisualTreeHelper.HitTest(AssociatedObject, pt);
if (result != null)
{
DependencyObject obj = result.VisualHit.ParentOfType<RadFluidContentControl>();
if (obj != null)
{
//trying to drag from Tile content area, not allowed.
return;
}
}
if (tileViewItem != null && tileView != null && tileView.MaximizedItem != null)
{
args.Data = tileViewItem;
var draggingImage = new Image
{
Source = new Telerik.Windows.Media.Imaging.RadBitmap(tileViewItem).Bitmap,
Width = tileViewItem.RestoredWidth,
Height = tileViewItem.RestoredHeight
};
if (tileView.MaximizedItem == tileViewItem)
{
args.DragVisualOffset = new Point(args.RelativeStartPoint.X - 50, args.RelativeStartPoint.Y-55);
}
args.DragVisual = draggingImage;
tileViewItem.Opacity = 0;
args.AllowedEffects = DragDropEffects.Move;
args.Handled = true;
// keep a copy of dragging tile
draggingTile = tileViewItem;
}
}
private void OnDragAndDropCompleted(object sender, DragDropCompletedEventArgs args)
{
if (args.OriginalSource.GetType() == typeof(RadTileViewItem))
{
if (AssociatedObject.MaximizedItem != null)
{
Point pt = Util.CorrectGetPosition((RadTileView)sender);
HitTestResult result = VisualTreeHelper.HitTest(AssociatedObject, pt);
if (result != null)
{
DependencyObject obj = result.VisualHit.ParentOfType<RadTileViewItem>();
if (obj != null)
{
((RadTileViewItem)obj).Position = draggingTile.Position;
draggingTile.Opacity = 100;
}
else
{
draggingTile.Opacity = 100;
}
}
else
{
draggingTile.Opacity = 100;
}
}
}
}
private void MyTileView_PreviewDragOver(object sender, System.Windows.DragEventArgs e)
{
FrameworkElement container = sender as FrameworkElement;
if (AssociatedObject.MaximizedItem != null)
{
if (container == null)
{
return;
}
double tolerance = 60;
double verticalPos = Util.CorrectGetPosition((RadTileView)container).Y;
double offset = 20;
ScrollViewer scrollViewer = AssociatedObject.FindChildByType<ScrollViewer>();
if (verticalPos < tolerance) // Top of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset); //Scroll up.
}
else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset); //Scroll down.
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
DragDropManager.RemoveDragInitializeHandler(AssociatedObject, OnDragInitialize);
DragDropManager.RemoveDragDropCompletedHandler(AssociatedObject, OnDragAndDropCompleted);
AssociatedObject.PreviewDragOver -= MyTileView_PreviewDragOver;
}
}
public static class Util
{
public static Point CorrectGetPosition(Visual relativeTo)
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return relativeTo.PointFromScreen(new Point(w32Mouse.X, w32Mouse.Y));
}
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
};
XAML would be like this
<telerik:RadTileView x:Name="MyTileView" ItemsSource={Binding TileItems}>
<i:Interaction.Behaviors>
<behaviors:RadTilesDragDropBehavior/>
</i:Interaction.Behaviors>
</telerik:RadTileView>
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
I know this is a duplication of Is there a (good/free) VirtualizingWrapPanel available for WPF?, but the answer there does not work as I need it. Example: When I click on an item in partial view, it takes me to the item below it, or when I refresh my items to let changes take effect on the UI that I do behind the scenes, it scrolls to the top. So has anyone found a good, free (or cheap) solution since last year?
Now I know there is an option at http://www.binarymission.co.uk/Products/WPF_SL/virtualizingwrappanel_wpf_sl.htm and that works exactly how I need it, I just would really prefer not to spend a grand for 1 project. Even if it was like 200-300 I would have already bought it but $900 for that one control in one project (so far), I just can't justify.
Any suggestions would be much appreciated!
Anthony F Greco
Use this one: http://virtualwrappanel.codeplex.com/ .
Here's how you use it:
xmlns:local="clr-namespace:MyWinCollection"
...
<local:VirtualizingWrapPanel ..../>
The one on your original link just needs a bit of tweaking to be able to support ScrollIntoView on the parent ListView so that when you refresh you can put the selection/scroll position back where you want it.
Add this extra override to the class mentioned in the question (found here)
protected override void BringIndexIntoView(int index)
{
var currentVisibleMin = _offset.Y;
var currentVisibleMax = _offset.Y + _viewportSize.Height - ItemHeight;
var itemsPerLine = Math.Max((int)Math.Floor(_viewportSize.Width / ItemWidth), 1);
var verticalOffsetRequiredToPutItemAtTopRow = Math.Floor((double)index / itemsPerLine) * ItemHeight;
if (verticalOffsetRequiredToPutItemAtTopRow < currentVisibleMin) // if item is above visible area put it on the top row
SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow);
else if (verticalOffsetRequiredToPutItemAtTopRow > currentVisibleMax) // if item is below visible area move to put it on bottom row
SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow - _viewportSize.Height + ItemHeight);
}
While you are there the following couple of functions can be changed as below to improve performance
protected override Size MeasureOverride(Size availableSize)
{
if (_itemsControl == null)
{
return availableSize;
}
_isInMeasure = true;
_childLayouts.Clear();
var extentInfo = GetExtentInfo(availableSize, ItemHeight);
EnsureScrollOffsetIsWithinConstrains(extentInfo);
var layoutInfo = GetLayoutInfo(availableSize, ItemHeight, extentInfo);
RecycleItems(layoutInfo);
// Determine where the first item is in relation to previously realized items
var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);
var visualIndex = 0;
var currentX = layoutInfo.FirstRealizedItemLeft;
var currentY = layoutInfo.FirstRealizedLineTop;
using (_itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true))
{
for (var itemIndex = layoutInfo.FirstRealizedItemIndex; itemIndex <= layoutInfo.LastRealizedItemIndex; itemIndex++, visualIndex++)
{
bool newlyRealized;
var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
SetVirtualItemIndex(child, itemIndex);
if (newlyRealized)
{
InsertInternalChild(visualIndex, child);
}
else
{
// check if item needs to be moved into a new position in the Children collection
if (visualIndex < Children.Count)
{
if (Children[visualIndex] != child)
{
var childCurrentIndex = Children.IndexOf(child);
if (childCurrentIndex >= 0)
{
RemoveInternalChildRange(childCurrentIndex, 1);
Debug.WriteLine("Moving child from {0} to {1}", childCurrentIndex, visualIndex);
}
else
Debug.WriteLine("Inserting child {0}", visualIndex);
InsertInternalChild(visualIndex, child);
}
}
else
{
// we know that the child can't already be in the children collection
// because we've been inserting children in correct visualIndex order,
// and this child has a visualIndex greater than the Children.Count
AddInternalChild(child);
Debug.WriteLine("Adding child at {0}", Children.Count-1);
}
}
// only prepare the item once it has been added to the visual tree
_itemsGenerator.PrepareItemContainer(child);
if (newlyRealized)
child.Measure(new Size(ItemWidth, ItemHeight));
_childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight));
if (currentX + ItemWidth * 2 >= availableSize.Width)
{
// wrap to a new line
currentY += ItemHeight;
currentX = 0;
}
else
{
currentX += ItemWidth;
}
}
}
RemoveRedundantChildren();
UpdateScrollInfo(availableSize, extentInfo);
var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width,
double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);
_isInMeasure = false;
return desiredSize;
}
class RemoveRange
{
public int Start;
public int Length;
}
private void RecycleItems(ItemLayoutInfo layoutInfo)
{
Queue<RemoveRange> ranges = null;
int idx = 0;
RemoveRange nextRange = null;
foreach (UIElement child in Children)
{
var virtualItemIndex = GetVirtualItemIndex(child);
if (virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex)
{
var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
if (generatorPosition.Index >= 0)
{
_itemsGenerator.Recycle(generatorPosition, 1);
}
if (nextRange == null)
nextRange = new RemoveRange() { Start = idx, Length = 1 };
else
nextRange.Length++;
}
else if (nextRange!= null)
{
if (ranges== null)
ranges = new Queue<RemoveRange>();
ranges.Enqueue(nextRange);
nextRange = null;
}
SetVirtualItemIndex(child, -1);
idx++;
}
if (nextRange != null)
{
if (ranges == null)
ranges = new Queue<RemoveRange>();
ranges.Enqueue(nextRange);
}
int removed = 0;
if (ranges != null)
{
foreach (var range in ranges)
{
RemoveInternalChildRange(range.Start-removed, range.Length);
removed +=range.Length;
}
}
}
After trying to fix the issues of the http://virtualwrappanel.codeplex.com/ control I ended up with the solution from https://stackoverflow.com/a/13560758/5887121. I also added the BringIntoView method and a DP to set the orientation.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace Wpf.Controls
{
// class from: https://github.com/samueldjack/VirtualCollection/blob/master/VirtualCollection/VirtualCollection/VirtualizingWrapPanel.cs
// MakeVisible() method from: http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
{
private const double ScrollLineAmount = 16.0;
private Size _extentSize;
private Size _viewportSize;
private Point _offset;
private ItemsControl _itemsControl;
private readonly Dictionary<UIElement, Rect> _childLayouts = new Dictionary<UIElement, Rect>();
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
private static readonly DependencyProperty VirtualItemIndexProperty =
DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(VirtualizingWrapPanel), new PropertyMetadata(-1));
private IRecyclingItemContainerGenerator _itemsGenerator;
private bool _isInMeasure;
private static int GetVirtualItemIndex(DependencyObject obj)
{
return (int)obj.GetValue(VirtualItemIndexProperty);
}
private static void SetVirtualItemIndex(DependencyObject obj, int value)
{
obj.SetValue(VirtualItemIndexProperty, value);
}
#region Orientation
/// <summary>
/// Gets and sets the orientation of the panel.
/// </summary>
/// <value>The orientation of the panel.</value>
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
/// <summary>
/// Identifies the Orientation dependency property.
/// </summary>
/// <remarks>
/// Returns: The identifier for the Orientation dependency property.
/// </remarks>
public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));
#endregion Orientation
public double ItemHeight
{
get
{
return (double)GetValue(ItemHeightProperty);
}
set
{
SetValue(ItemHeightProperty, value);
}
}
public double ItemWidth
{
get
{
return (double)GetValue(ItemWidthProperty);
}
set
{
SetValue(ItemWidthProperty, value);
}
}
public VirtualizingWrapPanel()
{
if ( !DesignerProperties.GetIsInDesignMode(this) )
{
Dispatcher.BeginInvoke((Action)Initialize);
}
}
private void Initialize()
{
_itemsControl = ItemsControl.GetItemsOwner(this);
_itemsGenerator = (IRecyclingItemContainerGenerator)ItemContainerGenerator;
InvalidateMeasure();
}
protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
{
base.OnItemsChanged(sender, args);
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
if ( _itemsControl == null )
{
return availableSize;
}
_isInMeasure = true;
_childLayouts.Clear();
var extentInfo = GetExtentInfo(availableSize);
EnsureScrollOffsetIsWithinConstrains(extentInfo);
var layoutInfo = GetLayoutInfo(availableSize, (Orientation == Orientation.Vertical) ? ItemHeight : ItemWidth, extentInfo);
RecycleItems(layoutInfo);
// Determine where the first item is in relation to previously realized items
var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);
var visualIndex = 0;
Double currentX = layoutInfo.FirstRealizedItemLeft;
Double currentY = layoutInfo.FirstRealizedLineTop;
using ( _itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true) )
{
for ( var itemIndex = layoutInfo.FirstRealizedItemIndex ; itemIndex <= layoutInfo.LastRealizedItemIndex ; itemIndex++, visualIndex++ )
{
bool newlyRealized;
var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
SetVirtualItemIndex(child, itemIndex);
if ( newlyRealized )
{
InsertInternalChild(visualIndex, child);
}
else
{
// check if item needs to be moved into a new position in the Children collection
if ( visualIndex < Children.Count )
{
if ( Children[visualIndex] != child )
{
var childCurrentIndex = Children.IndexOf(child);
if ( childCurrentIndex >= 0 )
{
RemoveInternalChildRange(childCurrentIndex, 1);
}
InsertInternalChild(visualIndex, child);
}
}
else
{
// we know that the child can't already be in the children collection
// because we've been inserting children in correct visualIndex order,
// and this child has a visualIndex greater than the Children.Count
AddInternalChild(child);
}
}
// only prepare the item once it has been added to the visual tree
_itemsGenerator.PrepareItemContainer(child);
child.Measure(new Size(ItemWidth, ItemHeight));
_childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight));
if ( Orientation == Orientation.Vertical )
{
if ( currentX + ItemWidth * 2 > availableSize.Width )
{
// wrap to a new line
currentY += ItemHeight;
currentX = 0;
}
else
{
currentX += ItemWidth;
}
}
else
{
if ( currentY + ItemHeight * 2 > availableSize.Height )
{
// wrap to a new column
currentX += ItemWidth;
currentY = 0;
}
else
{
currentY += ItemHeight;
}
}
}
}
RemoveRedundantChildren();
UpdateScrollInfo(availableSize, extentInfo);
var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width,
double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);
_isInMeasure = false;
return desiredSize;
}
private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
{
if ( Orientation == Orientation.Vertical )
{
_offset.Y = Clamp(_offset.Y, 0, extentInfo.MaxVerticalOffset);
}
else
{
_offset.X = Clamp(_offset.X, 0, extentInfo.MaxHorizontalOffset);
}
}
private void RecycleItems(ItemLayoutInfo layoutInfo)
{
foreach ( UIElement child in Children )
{
var virtualItemIndex = GetVirtualItemIndex(child);
if ( virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex )
{
var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
if ( generatorPosition.Index >= 0 )
{
_itemsGenerator.Recycle(generatorPosition, 1);
}
}
SetVirtualItemIndex(child, -1);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach ( UIElement child in Children )
{
child.Arrange(_childLayouts[child]);
}
return finalSize;
}
private void UpdateScrollInfo(Size availableSize, ExtentInfo extentInfo)
{
_viewportSize = availableSize;
if ( Orientation == Orientation.Vertical )
{
_extentSize = new Size(availableSize.Width, extentInfo.ExtentHeight);
}
else
{
_extentSize = new Size(extentInfo.ExtentWidth, availableSize.Height);
}
InvalidateScrollInfo();
}
private void RemoveRedundantChildren()
{
// iterate backwards through the child collection because we're going to be
// removing items from it
for ( var i = Children.Count - 1 ; i >= 0 ; i-- )
{
var child = Children[i];
// if the virtual item index is -1, this indicates
// it is a recycled item that hasn't been reused this time round
if ( GetVirtualItemIndex(child) == -1 )
{
RemoveInternalChildRange(i, 1);
}
}
}
private ItemLayoutInfo GetLayoutInfo(Size availableSize, double itemHeightOrWidth, ExtentInfo extentInfo)
{
if ( _itemsControl == null )
{
return new ItemLayoutInfo();
}
// we need to ensure that there is one realized item prior to the first visible item, and one after the last visible item,
// so that keyboard navigation works properly. For example, when focus is on the first visible item, and the user
// navigates up, the ListBox selects the previous item, and the scrolls that into view - and this triggers the loading of the rest of the items
// in that row
if ( Orientation == Orientation.Vertical )
{
var firstVisibleLine = (int)Math.Floor(VerticalOffset / itemHeightOrWidth);
var firstRealizedIndex = Math.Max(extentInfo.ItemsPerLine * firstVisibleLine - 1, 0);
var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset;
var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeightOrWidth - VerticalOffset;
var firstCompleteLineTop = (firstVisibleLine == 0 ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight);
var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeightOrWidth);
var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + 2, _itemsControl.Items.Count - 1);
return new ItemLayoutInfo
{
FirstRealizedItemIndex = firstRealizedIndex,
FirstRealizedItemLeft = firstRealizedItemLeft,
FirstRealizedLineTop = firstRealizedItemTop,
LastRealizedItemIndex = lastRealizedIndex,
};
}
else
{
var firstVisibleColumn = (int)Math.Floor(HorizontalOffset / itemHeightOrWidth);
var firstRealizedIndex = Math.Max(extentInfo.ItemsPerColumn * firstVisibleColumn - 1, 0);
var firstRealizedItemTop = firstRealizedIndex % extentInfo.ItemsPerColumn * ItemHeight - VerticalOffset;
var firstRealizedItemLeft = (firstRealizedIndex / extentInfo.ItemsPerColumn) * itemHeightOrWidth - HorizontalOffset;
var firstCompleteColumnLeft = (firstVisibleColumn == 0 ? firstRealizedItemLeft : firstRealizedItemLeft + ItemWidth);
var completeRealizedColumns = (int)Math.Ceiling((availableSize.Width - firstCompleteColumnLeft) / itemHeightOrWidth);
var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedColumns * extentInfo.ItemsPerColumn + 2, _itemsControl.Items.Count - 1);
return new ItemLayoutInfo
{
FirstRealizedItemIndex = firstRealizedIndex,
FirstRealizedItemLeft = firstRealizedItemLeft,
FirstRealizedLineTop = firstRealizedItemTop,
LastRealizedItemIndex = lastRealizedIndex,
};
}
}
private ExtentInfo GetExtentInfo(Size viewPortSize)
{
if ( _itemsControl == null )
{
return new ExtentInfo();
}
if ( Orientation == Orientation.Vertical )
{
var itemsPerLine = Math.Max((int)Math.Floor(viewPortSize.Width / ItemWidth), 1);
var totalLines = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerLine);
var extentHeight = Math.Max(totalLines * ItemHeight, viewPortSize.Height);
return new ExtentInfo
{
ItemsPerLine = itemsPerLine,
TotalLines = totalLines,
ExtentHeight = extentHeight,
MaxVerticalOffset = extentHeight - viewPortSize.Height,
};
}
else
{
var itemsPerColumn = Math.Max((int)Math.Floor(viewPortSize.Height / ItemHeight), 1);
var totalColumns = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerColumn);
var extentWidth = Math.Max(totalColumns * ItemWidth, viewPortSize.Width);
return new ExtentInfo
{
ItemsPerColumn = itemsPerColumn,
TotalColumns = totalColumns,
ExtentWidth = extentWidth,
MaxHorizontalOffset = extentWidth - viewPortSize.Width
};
}
}
public void LineUp()
{
SetVerticalOffset(VerticalOffset - ScrollLineAmount);
}
public void LineDown()
{
SetVerticalOffset(VerticalOffset + ScrollLineAmount);
}
public void LineLeft()
{
SetHorizontalOffset(HorizontalOffset - ScrollLineAmount);
}
public void LineRight()
{
SetHorizontalOffset(HorizontalOffset + ScrollLineAmount);
}
public void PageUp()
{
SetVerticalOffset(VerticalOffset - ViewportHeight);
}
public void PageDown()
{
SetVerticalOffset(VerticalOffset + ViewportHeight);
}
public void PageLeft()
{
SetHorizontalOffset(HorizontalOffset - ItemWidth);
}
public void PageRight()
{
SetHorizontalOffset(HorizontalOffset + ItemWidth);
}
public void MouseWheelUp()
{
if ( Orientation == Orientation.Vertical )
{
SetVerticalOffset(VerticalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
}
else
{
MouseWheelLeft();
}
}
public void MouseWheelDown()
{
if ( Orientation == Orientation.Vertical )
{
SetVerticalOffset(VerticalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
}
else
{
MouseWheelRight();
}
}
public void MouseWheelLeft()
{
SetHorizontalOffset(HorizontalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
}
public void MouseWheelRight()
{
SetHorizontalOffset(HorizontalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
}
public void SetHorizontalOffset(double offset)
{
if ( _isInMeasure )
{
return;
}
offset = Clamp(offset, 0, ExtentWidth - ViewportWidth);
_offset = new Point(offset, _offset.Y);
InvalidateScrollInfo();
InvalidateMeasure();
}
public void SetVerticalOffset(double offset)
{
if ( _isInMeasure )
{
return;
}
offset = Clamp(offset, 0, ExtentHeight - ViewportHeight);
_offset = new Point(_offset.X, offset);
InvalidateScrollInfo();
InvalidateMeasure();
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
if ( rectangle.IsEmpty ||
visual == null ||
visual == this ||
!IsAncestorOf(visual) )
{
return Rect.Empty;
}
rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
var viewRect = new Rect(HorizontalOffset, VerticalOffset, ViewportWidth, ViewportHeight);
rectangle.X += viewRect.X;
rectangle.Y += viewRect.Y;
viewRect.X = CalculateNewScrollOffset(viewRect.Left, viewRect.Right, rectangle.Left, rectangle.Right);
viewRect.Y = CalculateNewScrollOffset(viewRect.Top, viewRect.Bottom, rectangle.Top, rectangle.Bottom);
SetHorizontalOffset(viewRect.X);
SetVerticalOffset(viewRect.Y);
rectangle.Intersect(viewRect);
rectangle.X -= viewRect.X;
rectangle.Y -= viewRect.Y;
return rectangle;
}
private static double CalculateNewScrollOffset(double topView, double bottomView, double topChild, double bottomChild)
{
var offBottom = topChild < topView && bottomChild < bottomView;
var offTop = bottomChild > bottomView && topChild > topView;
var tooLarge = (bottomChild - topChild) > (bottomView - topView);
if ( !offBottom && !offTop )
return topView;
if ( (offBottom && !tooLarge) || (offTop && tooLarge) )
return topChild;
return bottomChild - (bottomView - topView);
}
public ItemLayoutInfo GetVisibleItemsRange()
{
return GetLayoutInfo(_viewportSize, (Orientation == Orientation.Vertical) ? ItemHeight : ItemWidth, GetExtentInfo(_viewportSize));
}
protected override void BringIndexIntoView(int index)
{
if ( Orientation == Orientation.Vertical )
{
var currentVisibleMin = _offset.Y;
var currentVisibleMax = _offset.Y + _viewportSize.Height - ItemHeight;
var itemsPerLine = Math.Max((int)Math.Floor(_viewportSize.Width / ItemWidth), 1);
var verticalOffsetRequiredToPutItemAtTopRow = Math.Floor((double)index / itemsPerLine) * ItemHeight;
if ( verticalOffsetRequiredToPutItemAtTopRow < currentVisibleMin ) // if item is above visible area put it on the top row
SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow);
else if ( verticalOffsetRequiredToPutItemAtTopRow > currentVisibleMax ) // if item is below visible area move to put it on bottom row
SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow - _viewportSize.Height + ItemHeight);
}
else
{
var currentVisibleMin = _offset.X;
var currentVisibleMax = _offset.X + _viewportSize.Width - ItemWidth;
var itemsPerColumn = Math.Max((int)Math.Floor(_viewportSize.Height / ItemHeight), 1);
var horizontalOffsetRequiredToPutItemAtLeftRow = Math.Floor((double)index / itemsPerColumn) * ItemWidth;
if ( horizontalOffsetRequiredToPutItemAtLeftRow < currentVisibleMin ) // if item is left from the visible area put it at the left most column
SetHorizontalOffset(horizontalOffsetRequiredToPutItemAtLeftRow);
else if ( horizontalOffsetRequiredToPutItemAtLeftRow > currentVisibleMax ) // if item is right from the visible area put it at the right most column
SetHorizontalOffset(horizontalOffsetRequiredToPutItemAtLeftRow - _viewportSize.Width + ItemWidth);
}
}
public bool CanVerticallyScroll
{
get;
set;
}
public bool CanHorizontallyScroll
{
get;
set;
}
public double ExtentWidth
{
get
{
return _extentSize.Width;
}
}
public double ExtentHeight
{
get
{
return _extentSize.Height;
}
}
public double ViewportWidth
{
get
{
return _viewportSize.Width;
}
}
public double ViewportHeight
{
get
{
return _viewportSize.Height;
}
}
public double HorizontalOffset
{
get
{
return _offset.X;
}
}
public double VerticalOffset
{
get
{
return _offset.Y;
}
}
public ScrollViewer ScrollOwner
{
get;
set;
}
private void InvalidateScrollInfo()
{
if ( ScrollOwner != null )
{
ScrollOwner.InvalidateScrollInfo();
}
}
private static void HandleItemDimensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wrapPanel = (d as VirtualizingWrapPanel);
if ( wrapPanel != null )
wrapPanel.InvalidateMeasure();
}
private double Clamp(double value, double min, double max)
{
return Math.Min(Math.Max(value, min), max);
}
internal class ExtentInfo
{
public int ItemsPerLine;
public Int32 ItemsPerColumn;
public int TotalLines;
public Int32 TotalColumns;
public double ExtentHeight;
public double ExtentWidth;
public double MaxVerticalOffset;
public double MaxHorizontalOffset;
}
public class ItemLayoutInfo
{
public int FirstRealizedItemIndex;
public double FirstRealizedLineTop;
public double FirstRealizedItemLeft;
public int LastRealizedItemIndex;
}
}
}
I was searching a week for some solution and than I had idea how to fake VirtualizingWrapPanel.
If you know width of your Items, than you can create "rows" as StackPanel and these rows render in VirtualizingStackPanel.
XAML
<ItemsControl x:Name="Thumbs" VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Pixel" ScrollViewer.CanContentScroll="True">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding ThumbWidth}" Height="{Binding ThumbHeight}"
BorderThickness="2" BorderBrush="Black" ClipToBounds="True">
<Image Source="{Binding FilePathCacheUri}" Stretch="Fill" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
C#
public List<BaseMediaItem> MediaItems { get; set; }
public List<List<BaseMediaItem>> SplitedMediaItems { get; set; }
public MainWindow() {
InitializeComponent();
DataContext = this;
MediaItems = new List<BaseMediaItem>();
SplitedMediaItems = new List<List<BaseMediaItem>>();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) {
LoadMediaItems();
SplitMediaItems();
Thumbs.ItemsSource = SplitedMediaItems;
}
public void LoadMediaItems() {
var sizes = new List<Point> {new Point(320, 180), new Point(320, 240), new Point(180, 320), new Point(240, 320)};
MediaItems.Clear();
var random = new Random();
for (var i = 0; i < 5000; i++) {
var size = sizes[random.Next(sizes.Count)];
MediaItems.Add(new BaseMediaItem($"Item {i}") {
ThumbWidth = (int)size.X,
ThumbHeight = (int)size.Y
});
}
}
public void SplitMediaItems() {
foreach (var itemsGroup in SplitedMediaItems) {
itemsGroup.Clear();
}
SplitedMediaItems.Clear();
var groupMaxWidth = Thumbs.ActualWidth;
var groupWidth = 0;
const int itemOffset = 6; //border, margin, padding, ...
var row = new List<BaseMediaItem>();
foreach (var item in MediaItems) {
if (item.ThumbWidth + itemOffset <= groupMaxWidth - groupWidth) {
row.Add(item);
groupWidth += item.ThumbWidth + itemOffset;
}
else {
SplitedMediaItems.Add(row);
row = new List<BaseMediaItem>();
row.Add(item);
groupWidth = item.ThumbWidth + itemOffset;
}
}
SplitedMediaItems.Add(row);
}