How can I animate the the scrolling for ListBox? I know I can use scrollIntoView but how can I animate it? I want to press the arrow keys to move from one listBoxItem to another.
Here is a rough implementation based on the same approach as the following link
http://aniscrollviewer.codeplex.com/
The VerticalOffset property is read-only so instead you can use an attached property VerticalOffset on the ScrollViewer which in turn does ScrollToVerticalOffset. This attached property can be animated.
You can also create an extension method for ItemsControl called AnimateScrollIntoView.
Call it like this
listBox.AnimateScrollIntoView(yourItem);
ScrollViewerBehavior
public class ScrollViewerBehavior
{
public static DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset",
typeof(double),
typeof(ScrollViewerBehavior),
new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void SetVerticalOffset(FrameworkElement target, double value)
{
target.SetValue(VerticalOffsetProperty, value);
}
public static double GetVerticalOffset(FrameworkElement target)
{
return (double)target.GetValue(VerticalOffsetProperty);
}
private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = target as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
ItemsControlExtensions
public static class ItemsControlExtensions
{
public static void AnimateScrollIntoView(this ItemsControl itemsControl, object item)
{
ScrollViewer scrollViewer = VisualTreeHelpers.GetVisualChild<ScrollViewer>(itemsControl);
UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
int index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
double toValue = scrollViewer.ScrollableHeight * ((double)index / itemsControl.Items.Count);
Point relativePoint = container.TranslatePoint(new Point(0.0, 0.0), Window.GetWindow(container));
DoubleAnimation verticalAnimation = new DoubleAnimation();
verticalAnimation.From = scrollViewer.VerticalOffset;
verticalAnimation.To = toValue;
verticalAnimation.DecelerationRatio = .2;
verticalAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(verticalAnimation);
Storyboard.SetTarget(verticalAnimation, scrollViewer);
Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollViewerBehavior.VerticalOffsetProperty));
storyboard.Begin();
}
}
And since you also need to get a hold of the ScrollViewer you'll need this
public static class VisualTreeHelpers
{
public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
Take a look at this article, it explains how animate scrolling and add touch gestures. Download the source at the bottom of the page and look at WpfScrollContent solution. I would extend the WPF ListBox and add the scroll animation to it that way you can reuse the control.
Related
TextBox has a default context menu. I would like to add an item to it. OK, that means cloning the default one, and adding an extra item to that.
I'd like to reuse some code here. I have five textboxes. Each needs the additional item on its context menu. The item needs act on the textbox that was clicked. I know "copy and paste" is the recommended method of code reuse in WPF, but if possible I'd prefer not to define five menus in XAML and five commands in the code behind.
Is there any reasonably clean and quick way to do this in WPF?
public partial class MyGhastlyView
{
/* blah blah */
private void MenuCut_Click(object sender, RoutedEventArgs e)
{
try
{
(sender as MenuItem).GetPlacementTarget<TextBox>().Cut();
}
catch (Exception)
{
}
}
/* blah blah */
}
public static class FurshlugginerExtensions
{
public static bool TryGetPlacementTarget<TTargetType>(this MenuItem mi,
out TTargetType target) where TTargetType : class
{
target = null;
var cm = mi.GetContextMenu();
if (null != cm)
{
target = cm.PlacementTarget as TTargetType;
}
return null != target;
}
public static TTargetType GetPlacementTarget<TTargetType>(this MenuItem mi)
where TTargetType : class
{
var cm = mi.GetContextMenu();
return (cm == null)
? null
: cm.PlacementTarget as TTargetType;
}
public static ContextMenu GetContextMenu(this MenuItem mi)
{
var logicalParent = LogicalTreeHelper.GetParent(mi);
if (logicalParent is ContextMenu)
{
return logicalParent as ContextMenu;
}
else if (logicalParent is MenuItem)
{
return (logicalParent as MenuItem).GetContextMenu();
}
return null;
}
}
UPDATE
What I'm looking for turns out to be a RoutedUICommand, with some futzing around in XAML. It knows what you clicked on (with some Kafkaesque exceptions due to event bubbling -- but can just set the CommandParameter on the ContextMenu).
Unfortunately, ContextMenuOpening event will not work here. For whatever reason, TextBox does not expose its context menu, and is always null unless you set it with your own. Perhaps it simply pops a private menu on right mouse click.
Charles Petzold speaks about that with RichTextBox here. (Both TextBox and RichTextBox derive from TextBoxBase, which appears to define that behavior)
It seems you will have to create your own, and duplicate the existing items.
Several articles demonstrate exactly this, like the one here.
Hope this helps.
EDIT:
However if you insist on editing the current menu, it appears someone has done so here (using an extension method and reflection).
After further investigation of the above attempt, it seems that the author is creating an instance of an EditorContextMenu (private class which derives from ContextMenu in System.Windows.Documents) and assigning it to the TextBox ContextMenu property, then adding the parameter menu items to the newly created menu. In effect, overriding the current menu. While you do get the original implementation, I am not sure I would favor this solution.
EDIT 2:
The following code will create only one instance of custom menu, bind Ctrl-D to the textboxes, along with the correlating ContextMenu item.
public static RoutedCommand ItemActionCommand = new RoutedCommand();
public MainWindow()
{
InitializeComponent();
CommandBinding commandBinding = new CommandBinding(ItemActionCommand, new ExecutedRoutedEventHandler(ItemActionCommandEventHandler));
KeyBinding keyBinding = new KeyBinding(ItemActionCommand, new KeyGesture(Key.D, ModifierKeys.Control));
MenuItem item = new MenuItem();
item.Click += CustomContextMenuItem_Click; // not really necessary
item.Header = "Custom Menu Item";
item.InputGestureText = "Ctrl+D";
item.Command = ItemActionCommand;
ContextMenu menu = new ContextMenu();
menu.Items.Add(item);
Grid container = new Grid();
this.Content = container;
for (int i = 0; i < 5; i++)
container.Children.Add(this.CreateTextBox("Value: " + i.ToString(), (i + 1) * 30.0d, menu, commandBinding, keyBinding));
}
private void ItemActionCommandEventHandler(object sender, ExecutedRoutedEventArgs e)
{
TextBox textBox = e.Source as TextBox;
Debug.Assert(textBox != null);
// perform actions against textbox here
}
private void CustomContextMenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem item = sender as MenuItem;
Debug.Assert(item != null);
TextBox textBox = ((ContextMenu)item.Parent).PlacementTarget as TextBox;
Debug.Assert(textBox != null);
// no need to do anything here since the command handler above will fire
// but for the sake of completeness
}
private TextBox CreateTextBox(string text, double topOffset, ContextMenu menu, CommandBinding commandBinding, KeyBinding keyBinding)
{
TextBox textbox = new TextBox();
textbox.HorizontalAlignment = HorizontalAlignment.Center;
textbox.VerticalAlignment = VerticalAlignment.Top;
textbox.Margin = new Thickness(0.0d, topOffset, 0.0d, 0.0d);
textbox.CommandBindings.Add(commandBinding);
textbox.InputBindings.Add(keyBinding);
textbox.ContextMenu = menu;
textbox.Width = 150.0d;
textbox.Height = 25.0d;
textbox.Text = text;
return textbox;
}
Screenshot:
It is possible with an AttachedProperty and the handling of the ContextMenuOpening event. Look here and here. Should take around 100 lines of code and one line in xaml.
For completenes sake:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1
{
public class CustomMenuAction
{
public static bool GetHasMenuItemAction(DependencyObject obj)
{
return (bool)obj.GetValue(HasMenuItemActionProperty);
}
public static void SetHasMenuItemAction(DependencyObject obj, bool value)
{
obj.SetValue(HasMenuItemActionProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HasMenuItemActionProperty =
DependencyProperty.RegisterAttached("HasMenuItemAction", typeof(bool), typeof(CustomMenuAction), new PropertyMetadata(default(bool),OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if((bool)e.NewValue)
{
var textbox = d as TextBox;
if(textbox != null)
{
textbox.ContextMenu = GetCustomContextMenu();
textbox.ContextMenuOpening += textbox_ContextMenuOpening;
}
}
}
private static ContextMenu GetCustomContextMenu()
{
var contextMenu = new ContextMenu();
var standardCommands = GetStandardCommands();
foreach (var item in standardCommands)
{
contextMenu.Items.Add(item);
}
return contextMenu;
}
private static IList<MenuItem> GetStandardCommands()
{
//From https://stackoverflow.com/a/210981/3411327
List<MenuItem> standardCommands = new List<MenuItem>();
MenuItem item = new MenuItem();
item.Command = ApplicationCommands.Cut;
standardCommands.Add(item);
item = new MenuItem();
item.Command = ApplicationCommands.Copy;
standardCommands.Add(item);
item = new MenuItem();
item.Command = ApplicationCommands.Paste;
standardCommands.Add(item);
return standardCommands;
}
static void textbox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
//From MSDN example: http://msdn.microsoft.com/en-us/library/bb613568.aspx
var textbox = e.Source as TextBox;
ContextMenu cm = textbox.ContextMenu;
foreach (MenuItem mi in cm.Items)
{
if ((String)mi.Header == "Item4") return;
}
MenuItem mi4 = new MenuItem();
mi4.Header = "Item4";
mi4.Click += (o, args) =>
{
var menuItem = o as MenuItem;
MessageBox.Show(menuItem.Header.ToString(), textbox.Text);
};
textbox.ContextMenu.Items.Add(mi4);
}
}
}
<TextBox namespace:CustomMenuAction.HasMenuItemAction="True"></TextBox>
I want to display an 8x8 Grid in Windows 8 metro app. To do this:
I created a Grid, and added 8 row definitions and 8 column definitions.
I then add a Rectangle with a black border to each of the grid cells.
Then in the MeasureOverride method, I check the availableSize. Since my grid needs to be square (aspect ratio = 1.0), I compute the minimum of availableSize.Width, availableSize.Height and return a new Size equal to (minimum, minimum).
However this does not work. The resulting grid's size is equal to availableSize, and not the size I return from my MeasureOverride method. If I modify the MeaureOverride, so that I set Height of RowDefinitions to minimum, and Width of ColumnDefinitions to minimum, then it works. But I saw some videos and they say you should not be explicitly setting Height & Width properties of anything.
So, is there a better way to accomplish what I want?
I'm not sure if you need to interact with those cells in any way, but if you just want to draw a grid, here is a quick control to do it. It will fill the space of the parent control.
public class GridShape : Control
{
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public static readonly DependencyProperty RowsProperty = DependencyProperty.Register("Rows", typeof(int), typeof(GridShape), new PropertyMetadata(8));
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(GridShape), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(GridShape), new PropertyMetadata(1.0));
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Pen pen = new Pen(Stroke, StrokeThickness);
double heightSpan = ActualHeight / Rows;
double widthSpan = ActualWidth / Columns;
for (double y = 0; y <= ActualHeight; y += heightSpan)
drawingContext.DrawLine(pen, new Point(0, y), new Point(ActualWidth, y));
for (double x = 0; x <= ActualWidth; x += widthSpan)
drawingContext.DrawLine(pen, new Point(x, 0), new Point(x, ActualHeight));
}
}
One solution is to create a custom Grid control to handle the width, height
public class SquareGrid : Grid
{
public SquareGrid()
{
this.SizeChanged += OnSizeChanged;
this.Loaded += OnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent == null) return;
parent.SizeChanged += ParentOnSizeChanged;
}
private void ParentOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
FrameworkElement parent = sender as FrameworkElement;
if (parent == null) return;
ResizeToSquare(parent);
}
private void ResizeToSquare(FrameworkElement parent)
{
var min = Math.Min(parent.ActualHeight, parent.ActualWidth);
this.Width = min;
this.Height = min;
}
}
You could also build a Behavior for this that would do the same thing.
There is an very old WPF application of Hyper Tree - http://blogs.msdn.com/b/llobo/archive/2007/10/31/mindmap-app-using-hyperbolic-tree.aspx.
The source code can be found at codeplax.com -
http://hypertree.codeplex.com/releases/view/11524
I wanted to use this tree control in my silverlight application. Now the issue is that i am new to silverlight, and the code is using some WPF specific things.
Please suggest me to solve my problem.
Thanks in advance.
Abhinav
Update:
things like
FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions, InvalidateVisual(), OnRender override, child UIElements.
Code Added:
public class SmartBorder : Decorator
{
#region Dependency Properties
public static readonly DependencyProperty GlowBrushProperty =
DependencyProperty.Register("GlowBrush", typeof(Brush), typeof(SmartBorder), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
......
#region Dependency Property backing CLR properties
......
#endregion
// if the button is pressed, this fires
private static void OnRenderIsPressedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
if ((bool)e.NewValue == true)
{
border.BorderBrush = Brushes.Transparent;
border.BorderWidth = 2;
}
else
{
border.BorderBrush = Brushes.Red;
border.BorderWidth = 2;
}
border.InvalidateVisual();
}
}
// if the mouse is over the control, this fires
private static void OnRenderIsMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmartBorder border = o as SmartBorder;
if (border != null)
{
border.InvalidateVisual();
}
}
// a series of methods which all make getting the default or currently selected brush easier
protected override void OnRender(DrawingContext dc)
{
Rect rc = new Rect(0, 0, this.ActualWidth, this.ActualHeight);
LinearGradientBrush gradientOverlay = GetGradientOverlay();
Brush glowBrush = GetGlowBrush();
Brush backBrush = GetBackgroundBrush();
Brush borderBrush = GetBorderBrush();
Pen borderPen = new Pen(borderBrush, BorderWidth);
double cornerRadiusCache = CornerRadius;
// draw the highlight as necessary
if (RenderIsMouseOver)
{
Rect rcGlow = rc;
double glowMove = BorderWidth * 2;
rcGlow.Inflate(glowMove, glowMove);
glowMove = 0;
rcGlow.Offset(new Vector(glowMove, glowMove));
dc.DrawRoundedRectangle(GetOuterGlowBrush(), null, rcGlow, cornerRadiusCache, cornerRadiusCache);
}
// we want to clip anything that might errantly draw outside of the smart border control
dc.PushClip(new RectangleGeometry(rc, cornerRadiusCache, cornerRadiusCache));
dc.DrawRoundedRectangle(backBrush, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.DrawRoundedRectangle(gradientOverlay, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
if (!RenderIsPressed)
{
double clipBorderSize = BorderWidth * -4.0;
Rect rcClip = rc;
rcClip.Offset(clipBorderSize, clipBorderSize);
rcClip.Inflate(-clipBorderSize, -clipBorderSize);
dc.PushClip(new RectangleGeometry(rcClip, cornerRadiusCache, cornerRadiusCache));
dc.DrawEllipse(glowBrush, null, new Point(this.ActualWidth / 2, this.ActualHeight * 0.10), this.ActualWidth * 0.80, this.ActualHeight * 0.40);
dc.Pop();
}
// just draw the border now to make sure it overlaps everything nicely
dc.DrawRoundedRectangle(null, borderPen, rc, cornerRadiusCache, cornerRadiusCache);
dc.Pop();
//base.OnRender(drawingContext);
}
protected override Size MeasureOverride(Size constraint)
{
UIElement child = this.Child as UIElement;
double borderThickness = BorderWidth * 2.0;
if (child != null)
{
...
}
return new Size(Math.Min(borderThickness, constraint.Width), Math.Min(borderThickness, constraint.Height));
}
}
Regarding FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions and value coercions etc. for Silverlight see the WPF_Compatibility solution under the ClipFlair codebase (http://clipflair.codeplex.com)
I currently have a ListBox whose ItemsSource collection is bound to a property on my viewmodel, of type IEnumerable. When that preoprty's reference changes, the ListBox updates as expected, however I have a problem in that if I have a large collection of items and scroll to the bottom of the ListBox, and then change the reference to another collection containing, say, 1 item, the ListBox view is blank and no scrollbar is displayed. I have to then scroll the listbox up with the mouse wheel, until the 1 item comes into view.
So, what I think I'm after, is a way of resetting the scroll position of the ListBox to the top, whenever the ItemsSource property changes, so that something is always displayed no matter how large or small the collection.
I'm unable to reproduce your problem (for me, the ListBox is scrolled to the last item in the new collection when changing ItemsSource). Anyway, to scroll the ListBox to the top every time its ItemsSource changes you can use some code behind. First listen to changes in the ItemsSourceProperty and then scroll the ListBox to the top once its items has been generated
Update
Made an attached behavior that does this instead to avoid code behind. It can be used like this
<ListBox ...
behaviors:ScrollToTopBehavior.ScrollToTop="True"/>
ScrollToTopBehavior
public static class ScrollToTopBehavior
{
public static readonly DependencyProperty ScrollToTopProperty =
DependencyProperty.RegisterAttached
(
"ScrollToTop",
typeof(bool),
typeof(ScrollToTopBehavior),
new UIPropertyMetadata(false, OnScrollToTopPropertyChanged)
);
public static bool GetScrollToTop(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollToTopProperty);
}
public static void SetScrollToTop(DependencyObject obj, bool value)
{
obj.SetValue(ScrollToTopProperty, value);
}
private static void OnScrollToTopPropertyChanged(DependencyObject dpo,
DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = dpo as ItemsControl;
if (itemsControl != null)
{
DependencyPropertyDescriptor dependencyPropertyDescriptor =
DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
if (dependencyPropertyDescriptor != null)
{
if ((bool)e.NewValue == true)
{
dependencyPropertyDescriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
}
else
{
dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
}
}
}
}
static void ItemsSourceChanged(object sender, EventArgs e)
{
ItemsControl itemsControl = sender as ItemsControl;
EventHandler eventHandler = null;
eventHandler = new EventHandler(delegate
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(itemsControl) as ScrollViewer;
scrollViewer.ScrollToTop();
itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
}
});
itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
}
}
And an implementation of GetVisualChild
private T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
Late answer:
A simple solution is to add an event handler for the TargetUpdated event, and set NotifyOnTargetUpdated=True on the ItemsSource binding:
<ListBox x:Name="listBox"
ItemsSource="{Binding MySource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
and in the event handler, scroll to the top item:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (listBox.Items.Count > 0)
{
listBox.ScrollIntoView(listBox.Items[0]);
}
}
Try this:
if (listBox.Items.Count > 0) {
listBox.ScrollIntoView(listBox.Items[0]);
}
Improved Fredrik Hedblad's answer to work with ObservableCollection:
public static class ItemsControlAttachedProperties
{
#region ScrollToTopOnItemsSourceChange Property
public static readonly DependencyProperty ScrollToTopOnItemsSourceChangeProperty =
DependencyProperty.RegisterAttached(
"ScrollToTopOnItemsSourceChange",
typeof(bool),
typeof(ItemsControlAttachedProperties),
new UIPropertyMetadata(false, OnScrollToTopOnItemsSourceChangePropertyChanged));
public static bool GetScrollToTopOnItemsSourceChange(DependencyObject obj)
{
return (bool) obj.GetValue(ScrollToTopOnItemsSourceChangeProperty);
}
public static void SetScrollToTopOnItemsSourceChange(DependencyObject obj, bool value)
{
obj.SetValue(ScrollToTopOnItemsSourceChangeProperty, value);
}
static void OnScrollToTopOnItemsSourceChangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var itemsControl = obj as ItemsControl;
if (itemsControl == null)
{
throw new Exception("ScrollToTopOnItemsSourceChange Property must be attached to an ItemsControl based control.");
}
DependencyPropertyDescriptor descriptor =
DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
if (descriptor != null)
{
if ((bool) e.NewValue)
{
descriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
}
else
{
descriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
}
}
}
static void ItemsSourceChanged(object sender, EventArgs e)
{
var itemsControl = sender as ItemsControl;
DoScrollToTop(itemsControl);
var collection = itemsControl.ItemsSource as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += (o, args) => DoScrollToTop(itemsControl);
}
}
static void DoScrollToTop(ItemsControl itemsControl)
{
EventHandler eventHandler = null;
eventHandler =
delegate
{
if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
var scrollViewer = GetVisualChild<ScrollViewer>(itemsControl);
scrollViewer.ScrollToTop();
itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
}
};
itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
}
static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < numVisuals; i++)
{
var v = (Visual) VisualTreeHelper.GetChild(parent, i);
child = v as T ?? GetVisualChild<T>(v);
if (child != null)
{
break;
}
}
return child;
}
#endregion
}
When you format the control, you select a range of cells as the selection choices which are then listed in the list box. You also select a cell as the link to the selected choices in which a number will be displayed depending on the position of the selection in the list. 1 for first in the list, 2 for second etc. The code is quite simply:-
Range("A1")Select
Selection = 1
Change ("A1") to the cell you have linked
and change the 1 to the position in the list you want selected.
The cell reference being a link works both ways - if you change your selection, the number in the cell changes and if you change the number in the cell, the highlighted selection changes.
How to raise a event in usercontrol from the another usercontrol. i tried to do with delegates, but it doesnt work. How can i do this. am using C#(WPF)
usercontrol1.cs
public partial class UserControl1 : UserControl
{
delegate void myDelegate();
public UserControl1()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
UserControl2 obj = new UserControl2();
myDelegate d = new myDelegate(obj.CallDelegate);
obj.CallDelegate();
}
}
Usercontrol2.cs
public partial class UserControl2 : UserControl
{
public UserControl2()
{
InitializeComponent();
}
public void CallDelegate()
{
this.Width = 50;
this.Height = 50;
MessageBox.Show("Method called ");
}
}
when i use delegate i can go get the messagebox from the method, but the control doesnt resize. do i want to render it again ?? i have tried to assign explicitly, but doesnt work
In general, only the event owner can raise an event. There are exceptions to this (such as with Button.PerformClick in Windows Forms, but they have to be specifically provided by the class in question.
It's possible that WPF routed events may give an alternative here, but you haven't been clear about what kind of events you're talking about. An example of what you're trying to do would be helpful.
that's because in your code you raise an event on a new UserControl2. for your specific example the code of the UserControl1.button1_Click event should be like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
if (this.Parent != null && this.Parent is StackPanel)
{
StackPanel parentControl = this.Parent as StackPanel;
foreach (UIElement child in parentControl.Children)
{
if (child is UserControl2)
((UserControl2)child).CallDelegate();
}
}
}
EDIT:
kay so it seems you want to get all the usercontrol2 within the window1 to be resized. then what you need is to make a recursive function to get the topmost parent, e.g (modded from hardcodet.net/2008/02/find-wpf-parent)
DependencyObject GetHighestParent(DependencyObject child)
{
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
FrameworkElement frameworkElement = child as FrameworkElement;
if (frameworkElement != null)
{
DependencyObject parent = frameworkElement.Parent;
if (parent != null)
{
return GetHighestParent(parent);
}
else
{
return child;
}
}
DependencyObject visualParent = VisualTreeHelper.GetParent(child);
if (visualParent != null)
return GetHighestParent(visualParent);
else
return child;
}
then you might want to create a method to walkdown all the children like this:
void CallDelegateInAllControl2(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is UserControl2)
{
((UserControl2)child).CallDelegate();
}
else
{
CallDelegateInAllControl2(child);
}
}
}
and then you call it within button1_click event
private void button1_Click(object sender, RoutedEventArgs e)
{
DependencyObject parent = GetHighestParent(this);
if(parent!=null)
CallDelegateInAllControl2(parent);
}
note: a walk to get parent and child might be tricky and risky i think and i believe it's a long process so you might just want to re-layout your window1 so it has a StackPanel/Grid with a usercontrol1 element and all usercontrol2 elements within it so you can use the first code i post.