Get width of ComboBoxItem's - wpf

I've got combobox binded to string[].
I haven't got clear combobox items. But I want measure my dropown items.
How can I get width of items in combobox at runtime. I need this to manage width of my combo.

If you want to do this and you're not sure if all your ComboBoxItems have been generated then you can use this code. It will expand the ComboBox in code behind and when all the ComboBoxItems within it are loaded, measure their size, and then close the ComboBox.
The IExpandCollapseProvider is in UIAutomationProvider
public void SetComboBoxWidthFromItems()
{
double comboBoxWidth = c_comboBox.DesiredSize.Width;
// Create the peer and provider to expand the c_comboBox in code behind.
ComboBoxAutomationPeer peer = new ComboBoxAutomationPeer(c_comboBox);
IExpandCollapseProvider provider = (IExpandCollapseProvider)peer.GetPattern(PatternInterface.ExpandCollapse);
EventHandler eventHandler = null;
eventHandler = new EventHandler(delegate
{
if (c_comboBox.IsDropDownOpen &&
c_comboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
double width = 0;
foreach (var item in c_comboBox.Items)
{
ComboBoxItem comboBoxItem = c_comboBox.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
comboBoxItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (comboBoxItem.DesiredSize.Width > width)
{
width = comboBoxItem.DesiredSize.Width;
}
}
c_comboBox.Width = comboBoxWidth + width;
// Remove the event handler.
c_comboBox.ItemContainerGenerator.StatusChanged -= eventHandler;
c_comboBox.DropDownOpened -= eventHandler;
provider.Collapse();
}
});
// Anonymous delegate as event handler for ItemContainerGenerator.StatusChanged.
c_comboBox.ItemContainerGenerator.StatusChanged += eventHandler;
c_comboBox.DropDownOpened += eventHandler;
// Expand the c_comboBox to generate all its ComboBoxItem's.
provider.Expand();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
SetComboBoxWidthFromItems();
}

Try this function:
foreach(var item in MyComboBox.Items){
double width = item.ActualWidth;
}

Related

Attach to Event of Sibling Control in Custom WPF Control Initialization

I am trying to create a WPF custom slider control that acts as a scrollbar for a Listview. I'm doing this by putting the name of the listview in the Tag attribute of my custom slider and then using the slider's OnValueChange event to scroll the listview. This works great, however, when I scroll in the listview with my mousewheel the slider doesn't move. What I need is a way to attach a method to the listview's MouseWheel event when my custom slider initializes. Here is what I've tried:
Custom slider class:
public class LabeledScrollbar : Slider
{
public override void EndInit()
{
var listbox = (ListBox)this.FindName(this.Tag.ToString());
if (listbox != null)
{
listbox.MouseWheel += new MouseWheelEventHandler(this.OnMouseWheel);
}
base.EndInit();
}
protected void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
this.Value += 5;
}
protected override void OnValueChanged(double oldValue, double newValue)
{
var listBox = (ListBox)this.FindName(this.Tag.ToString());
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listBox.ItemsSource);
if (newValue == this.Maximum)
{
if (VisualTreeHelper.GetChildrenCount(listBox) > 0)
{
var chrome = VisualTreeHelper.GetChild(listBox, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listBox.ScrollIntoView(selectedItem);
}
}
}
XAML:
<ListView x:Name="listViewCategories">
...
</ListView>
<local:LabeledScrollbar x:Name="categoryScrollbar" Orientation="Vertical" TickPlacement="BottomRight" Tag="listViewCategories"></local:LabeledScrollbar>
While it seems like OnMouseWheel should fire when the I scroll in the listview, it's not happening and I haven't been able to find anything else to try. Is there a way to do what I want in WPF? I know I could put a method in the code behind of my view to make the MouseScroll event of the listview move the slider, but I was hoping to encapsulate as much of the logic for the slider in the slider class as possible.
So it seems that the trick was to use PreviewMouseWheel instead of MouseWheel. For future reference here is my current class:
/// <summary>
/// This class creates a custom control that can be used as a scrollbar for a listbox that displays the current
/// group visible in the listbox on a small label next to the slider thumb.
///
/// To use it, set the Tag value to the name of the listbox the scollbar will be controlling.
/// </summary>
public class LabeledScrollbar : Slider
{
//Tracks control initialization to ensure it only gets loaded once
private bool initialized = false;
//The listview this slider will control
private ListView listView;
public LabeledScrollbar(): base()
{
this.Loaded += LabeledScrollbar_Loaded;
}
void LabeledScrollbar_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
//The tag must be set to the name of a listbox
listView = (ListView)this.FindName(this.Tag.ToString());
if (listView != null && !this.initialized)
{
//Make sure that the mouse wheel event in the linked listbox is handled
listView.PreviewMouseWheel += (s, ev) =>
{
if (ev.Delta > 0)
this.Value += 3;
else
this.Value -= 3;
};
//Move scrollbar and list to the top if the collection changes
((INotifyCollectionChanged)listView.Items).CollectionChanged += (s, ev) =>
{
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
};
//Get the max value of the slider by checking the tag value and looking up the associated listbox
this.Maximum = ((ItemCollection)listView.Items).Count - 1;
this.Value = this.Maximum;
this.initialized = true;
}
}
protected override void OnValueChanged(double oldValue, double newValue)
{
//Refresh the tickbar so that it will render for a new value
InvalidateTickbar();
//Scroll the list box to the correct location
ScrollToIndex(newValue);
}
private void ScrollToIndex(double newValue)
{
if (newValue == this.Maximum)
{
//ScrollIntoView method does not scroll to the top so
//we need to access the scrollview to move the slider to the top
if (VisualTreeHelper.GetChildrenCount(listView) > 0)
{
var chrome = VisualTreeHelper.GetChild(listView, 0);
var scrollView = (ScrollViewer)VisualTreeHelper.GetChild(chrome, 0);
scrollView.ScrollToTop();
}
}
else
{
var collection = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
var index = (collection.Count - 1) - (int)Math.Floor(newValue);
var selectedItem = collection.GetItemAt(index);
listView.ScrollIntoView(selectedItem);
}
}
private void InvalidateTickbar()
{
//update the tickbar for the new position
if (VisualTreeHelper.GetChildrenCount(this) > 0)
{
var firstChild = VisualTreeHelper.GetChild(this, 0);
if (VisualTreeHelper.GetChildrenCount(firstChild) > 0)
{
var secondChild = (CustomTickBar)VisualTreeHelper.GetChild(firstChild, 0);
secondChild.InvalidateVisual();
}
}
}
}

WPF Context Menu

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>

How to get the Logical Children of a RowDefinition?

I have Extended the RowDefinition as RowDefinitionExtended and In that, when can i get the LogicalChildren belongs to this RowDefinition. I mean in which override can i get the LogicalChildren?
public class RowDefinitionExtended : RowDefinition
{
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Loaded += OnRowDefinitionExtendedLoaded;
}
void OnRowDefinitionExtendedLoaded(object sender, RoutedEventArgs e)
{
var parent = GetUIParentCore() as Grid;
if (parent == null) return;
if (parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this)).All(ctrl => ctrl.Visibility != Visibility.Visible))
Height = new GridLength(0);
}
}
What my requirement is, I need to check all the LogicalChildren to its Visibility and Change its Height accordingly.
How could i do this? Any idea?
Update:
Code has been updated, On Load I could do this and it works fine. But my problem is, am changing the controls visibility after load... So is there any notification while changing the Visibility? am looking a event when the layout updated like..
Any event can i use it for?
You can't do that by means of a derived RowDefinition, but this little helper method should do the job (if your intention was to get all child elements in a certain row of a Grid):
public static IEnumerable<UIElement> ChildrenInRow(Grid grid, int row)
{
return grid.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == row);
}
You have to subscribe to the IsVisibleChanged handler for each element in the row when the row is loaded.
When the visibility changed, you could do whatever you need
public class RowDefinitionExtended : RowDefinition
{
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
Loaded += OnRowDefinitionExtendedLoaded;
}
void OnRowDefinitionExtendedLoaded(object sender, RoutedEventArgs e)
{
var parent = GetUIParentCore() as Grid;
if (parent == null) return;
//Subscribe to the IsVisibleChanged handler for each element in the row
var ElementInGridRow = parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this));
foreach (var element in ElementInGridRow)
{
element.IsVisibleChanged+=new DependencyPropertyChangedEventHandler(OnChildrenIsVisibleChanged);
}
}
private void OnChildrenIsVisibleChanged(object sender,DependencyPropertyChangedEventArgs e)
{
UIElement element = sender as UIElement;
//Do stuff...
var parent = GetUIParentCore() as Grid;
if (parent.Children.Cast<UIElement>().Where(c => Grid.GetRow(c) == parent.RowDefinitions.IndexOf(this)).All(ctrl => ctrl.Visibility != Visibility.Visible))
Height = new GridLength(0);
}
}

How to create user define (new) event for user control in WPF ?one small example

I have one UserControl in which I am using a Canvas, and in that Canvas one Rectangle. I want to create a click event for that user control (Canvas and Rectangle) which I then want to use in the main window.
The question is: I want to create a new click event for the UserControl. How to do it? Kindly show little example or the code.
A brief example on how to expose an event from the UserControl that the main window can register:
In your UserControl:
1 . Add the following declaration:
public event EventHandler UserControlClicked;
2 . In your UserControl_Clicked event raise the event like this:
private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
{
if (UserControlClicked != null)
{
UserControlClicked(this, EventArgs.Empty);
}
}
In your MainWindow:
Your usercontrol will now have a UserControlClicked event which you can register to:
<local:UserControl1 x:Name="UC" UserControlClicked="UC_OnUserControlClicked" />
i find this easier for passing value to handler:
public event Action<string> onUserCodeFetched;
private void btnEnterClicked(object sender, RoutedEventArgs e)
{
onUserCodeFetched(PersonellCode.Text);
PersonellCode.Text = "";
}
It's an old question, but I also found useful to bypass the event using a property-like custom event as in here. You can use a general EventHandler or you can be more specific (RoutedEventHandler, MouseButtonEventHandler, etc) to avoid later casts. For instance, to bypass a click event, you could write in your custom control class:
public readonly object objectLock = new object();
public event RoutedEventHandler CustomClick
{
add{ lock (objectLock) { myClickableInnerControl.Click += value;}}
remove {lock (objectLock) { myClickableInnerControl.Click -= value; }}
}
and for a PreviewMouseDown event, it would be something like:
public readonly object objectLock = new object();
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add{ lock (objectLock) { myInnerControl.PreviewMouseDown += value;}}
remove {lock (objectLock) { myInnerControl.PreviewMouseDown -= value; }}
}
Where myInnerControl would be your canvas in this case.
Then you can initialize the event from xaml as
<local:myClickableControl x:Name="ClickCtrl1" CustomClick="ClickCtrl1_CustomClick" />
or
<local:myCustomControl x:Name="CustomCtrl1" CustomPreviewMouseDown="CustomCtrl1_CustomPreviewMouseDown" />
And from code behind:
ClickCtrl1.CustomClick+=ClickCtrl1_CustomClick;
or
CustomCtrl1.CustomPreviewMouseDown+=CustomCtrl1_CustomPreviewMouseDown;
You can also subscribe your callback to several inner controls events (being careful when they overlap as some events like previewMouseDown are not only fired by the front control but also by the controls underneath).
public readonly object objectLock = new object();
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add{ lock (objectLock)
{
myInnerControl1.PreviewMouseDown += value;
myInnerControl2.PreviewMouseDown += value;
}}
remove {lock (objectLock)
{
myInnerControl1.PreviewMouseDown -= value;
myInnerControl2.PreviewMouseDown -= value;
}}
}
(If the inner controls partially overlap, you can use the corresponding eventArgs Handled property in your callback method to avoid repetitions)
Finally, you can add a layer of control to the event fired by your inner control:
bool MousePreviewDownUsed = false;
event MouseButtonEventHandler _myPreviewMouseDownEvent = null;
public event MouseButtonEventHandler CustomPreviewMouseDown
{
add
{
lock (objectLock)
{
_myPreviewMouseDownEvent += value;
if (value != null && !MousePreviewDownUsed)
{
myInnerControl.PreviewMouseDown += myInnerControl_PreviewMouseDown;
MousePreviewDownUsed = true;
}
}
}
remove
{
lock (objectLock)
{
if (_myPreviewMouseDownEvent != null)
{
_myPreviewMouseDownEvent -= value;
if ((_myPreviewMouseDownEvent == null ||
_myPreviewMouseDownEvent.GetInvocationList().Length == 0)
&& MousePreviewDownUsed)
{
_myPreviewMouseDownEvent = null;
myInnerControl.PreviewMouseDown -= myInnerControl_PreviewMouseDown;
MousePreviewDownUsed = false;
}
}
}
}
}
private void myInnerControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// Do some previous operations or control whether the event must be broadcasted
_myPreviewMouseDownEvent?.Invoke(sender, e);
}
This event is initialized from xaml or code behind the same way as before.

WPF - reset ListBox scroll position when ItemsSource changes

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.

Resources