GridvView.SelectedCells.Add() does not update view - wpf

GridViews Selected cells is a IList, so it does not update the view, when i add selections from my ViewModel.
Is there a way to force updating the view for selected Cells. The way i currently uddate views is by having a Attached behavior, which updates the list on ViewModel, but also the GridView, but the GridView does not update its visuals.
here is my attached behavior:
public static List<GridCell> GetSelectedCells(DependencyObject obj)
{
return (List<GridCell>)obj.GetValue(SelectedCellsProperty);
}
public static void SetSelectedCells(DependencyObject obj, List<GridCell> value)
{
obj.SetValue(SelectedCellsProperty, value);
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.RegisterAttached("SelectedCells", typeof(List<GridCell>), typeof(DataGridHelper), new UIPropertyMetadata(null, OnSelectedCellsChanged));
static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty =
DependencyProperty.RegisterAttached(nameof(SelectedCellsChangedEventHandler), typeof(SelectedCellsChangedEventHandler), typeof(DataGridHelper), new UIPropertyMetadata(null));
private static bool NewResouce = false;
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is DataGrid)
{
NewResouce = true;
DataGrid datagrid = d as DataGrid;
if (GetSelectionChangedHandler(d) == null)
{
SelectedCellsChangedEventHandler selectionchanged = (sender, e) =>
{
if (!NewResouce)
{
List<GridCell> cells = new List<GridCell>();
foreach (var selectedell in datagrid.SelectedCells)
{
string header = selectedell.Column.Header.ToString();
GridCell cell = new GridCell
{
RowIndex = datagrid.Items.IndexOf(selectedell.Item),
ColumnIndex = selectedell.Column.DisplayIndex,
Parent = selectedell.Item as ExpandoObject,
ColumnHeader = header,
Value = (selectedell.Item as IDictionary<string, object>)[header]
};
cells.Add(cell);
}
SetSelectedCells(d, cells);
}
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
foreach (var selected in GetSelectedCells(d) as List<GridCell>)
{
DataGridCellInfo cell = new DataGridCellInfo(selected.Parent, datagrid.Columns[selected.ColumnIndex]);
if (!datagrid.SelectedCells.Contains(cell))
{
datagrid.SelectedCells.Add(cell);
}
}
NewResouce = false;
}
}
}
The reason why i have the NewResource boolean, is that the event selection changed does actually fire when I add newly selected items. Its just the view that does not update its selections.
The SelectedCells is added after the view is loaded, due to its located inside a tab, and it looks like the data on gridview is empty before view is loaded, so I cannot set selected before the view is loaded.

Answer is simple. Please use an ObservableCollection instead of the list.
The observable collection is a dependency object and it will raise a propertyChanged event to the view to notify it regarding the property change and the view will be updated.

I ended up finden some properties that does implement INotifyPropertyChanged, on DataGridCells heres the solution:
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is DataGrid)
{
DataGrid datagrid = d as DataGrid;
if (GetSelectionChangedHandler(d) == null)
{
SelectedCellsChangedEventHandler selectionchanged = (sender, e) =>
{
List<GridCell> cells = new List<GridCell>();
foreach (var selectedell in datagrid.SelectedCells)
{
string header = selectedell.Column.Header.ToString();
GridCell cell = new GridCell
{
RowIndex = datagrid.Items.IndexOf(selectedell.Item),
ColumnIndex = selectedell.Column.DisplayIndex,
Parent = selectedell.Item as ExpandoObject,
ColumnHeader = header,
Value = (selectedell.Item as IDictionary<string, object>)[header]
};
cells.Add(cell);
}
SetSelectedCells(d, cells);
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
foreach (var selected in GetSelectedCells(d) as List<GridCell>)
{
DataGridCell actualCell = datagrid.GetCell(selected.RowIndex, selected.ColumnIndex);
actualCell.IsSelected = true;
actualCell.Focus();
}
}
}
for it to work i added some extention method to datagrid, to make it easier to get the Cell here the extention methodes: (stolen from this blog)
public static T GetVisualChild<T>(Visual 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;
}
public static DataGridCell GetCell(this DataGrid grid, int rowIndex, int columnIndex)
{
return GetCell(grid, GetRow(grid, rowIndex), columnIndex);
}
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
grid.ScrollIntoView(row, grid.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public static DataGridRow GetRow(this DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// May be virtualized, bring into view and try again.
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}

Related

How can I hook the color changed event when I selected color is already selected?

I have color picker. If I selected different colors means it is fire color changed event. But I select color is already selected color, the Color changed event is not fired. So how can I achieve this requirement or how can hook event for when select color is already selected.
Xaml:
<system:SplitButton x:Name="Font_FontColor" Height="24" DataContext="{Binding ElementName=Font_FontColorPicker}">
<system:ColorPickerPalette x:Name="Font_FontColorPicker" system:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both"
IsExpanded="True"
MoreColorOptionVisibility="Collapsed"/>
C#
it is a control behavior. so can make the code for achieve this by below codes
XAML:
<syncfusion:SplitButton x:Name="Font_FontColor" Height="24"
DataContext="{Binding ElementName=Font_FontColorPicker}">
<syncfusion:ColorPickerPalette x:Name="Font_FontColorPicker"
syncfusion:SkinStorage.VisualStyle="Metro"
BlackWhiteVisibility="Both" IsExpanded="True" MoreColorOptionVisibility="Collapsed"
Color="Red" />
C#:
public partial class MainWindow : Window
{
bool CanHookEvents = true;
public MainWindow()
{
InitializeComponent();
Font_FontColorPicker.ColorChanged += Font_FontColorPicker_ColorChanged;
//Font_FontColor.IsDropDownOpenChanged += Font_FontColor_IsDropDownOpenChanged;
Font_FontColorPicker.Loaded += Font_FontColorPicker_Loaded;
}
private void Font_FontColorPicker_Loaded(object sender, RoutedEventArgs e)
{
if (CanHookEvents)
{
foreach (ColorGroupItem item in FindVisualChildrenOfType<ColorGroupItem>(Font_FontColorPicker))
{
if (item != null)
{
item.PreviewMouseLeftButtonDown += item_PreviewMouseLeftButtonDown;
}
}
}
}
void item_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if(Font_FontColorPicker.Color.Equals((((sender as ColorGroupItem).Color) as SolidColorBrush).Color))
{
// I have closed dropdown. Do your stuff here
Font_FontColor.IsDropDownOpen = false;
}
}
public static IEnumerable<T> FindVisualChildrenOfType<T>(DependencyObject parent)
where T : DependencyObject
{
List<T> foundChildren = new List<T>();
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foreach (var other in FindVisualChildrenOfType<T>(child))
yield return other;
}
else
{
yield return (T)child;
}
}
}
void Font_FontColorPicker_ColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Font_FontColor.IsDropDownOpen = false;
}
}

How to get datagrid selected row value in textbox

I want datagrid selected value in textbox.
I tried this code
private void datagrid1_SelectionChange(object Sender, RoutedEventArg e)
{
var selectedrow = datagrid1.selectedItem as datarowview;
var id = selectedrow["Tagid"]; // Here I get error that object reference is not set is an instance of an object
string s = conver.tostring(id);
txttextbox1.text= s;
}
It works fine.
But when I refresh (or reload) datagrid1 then selectionChange throws an error:
Object reference is not instance of an object
In your XAML
<DataGrid Name="MyGrid" AutoGenerateColumns="False" SelectionChanged="DataGrid_Details_SelectionChanged">
and in .cs file add
private void DataGrid_Details_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var row_list = GetDataGridRows(MyGrid);
foreach (DataGridRow single_row in row_list)
{
if (single_row.IsSelected == true)
{
//Get your value over here
}
}
}
catch { }
}
public IEnumerable<DataGridRow> GetDataGridRows(DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (null != row) yield return row;
}
}

Iterate over VisualTreeHelper.GetChild() and call UpdateSource on Controls that contain databinding

I have a ContentControl in WPF which contains some input Controls, like TextBoxes and ComboBoxes.
Each of these Controls is databound to a given property in the ViewModel, with UpdateSourceTrigger=Explicit.
When I click some "Submit" button, I want to traverse every Child of FormularioPaciente that has bindings, and call UpdateSource:
private void btnSalvarEditarPaciente_Click(object sender, System.Windows.RoutedEventArgs e) {
foreach (var childControl in LogicalTreeHelper.GetChildren(FormularioPaciente)) {
// what should I do now?
// I would really like to "auto-find" everything that should be updated...
}
}
I think you can update the solution a bit
void GetBindingsRecursive(DependencyObject dObj, List<BindingExpressions> bindingList)
{
bindingList.AddRange(DependencyObjectHelper.GetBindingObjects(dObj));
int childrenCount = VisualTreeHelper.GetChildrenCount(dObj);
if (childrenCount > 0)
{
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dObj, i);
GetBindingsRecursive(child, bindingList);
}
}
}
public static class DependencyObjectHelper
{
public static List<BindingExpression> GetBindingObjects(Object element)
{
List<BindingExpression> bindings = new List<BindingBase>();
List<DependencyProperty> dpList = new List<DependencyProperty>();
dpList.AddRange(GetDependencyProperties(element));
dpList.AddRange(GetAttachedProperties(element));
foreach (DependencyProperty dp in dpList)
{
BindingExpression b = BindingOperations.GetBindingExpression(element as DependencyObject, dp);
if (b != null)
{
bindings.Add(b);
}
}
return bindings;
}
public static List<DependencyProperty> GetDependencyProperties(Object element)
{
List<DependencyProperty> properties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.DependencyProperty != null)
{
properties.Add(mp.DependencyProperty);
}
}
}
return properties;
}
public static List<DependencyProperty> GetAttachedProperties(Object element)
{
List<DependencyProperty> attachedProperties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.IsAttached)
{
attachedProperties.Add(mp.DependencyProperty);
}
}
}
return attachedProperties;
}
}
and once you get the BindingExpression list you can call BindingExpression.UpdateSource() on those.

How to get WPF DataGridCell visual horizontal (X-axis) position?

I need to get the position of a WPF DataGridCell, obtained in a DataGrid cell changed event, but only can get the vertical (Y-axis).
The horizontal remains the same, despite a different column is pointed.
Here is the almost working code.
Test by clicking on different cells.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
List<Person> Persons = new List<Person>();
public MainWindow()
{
InitializeComponent();
Persons.Add(new Person { Id = 1, Name = "John", City = "London" });
Persons.Add(new Person { Id = 2, Name = "Charles", City = "Rome" });
Persons.Add(new Person { Id = 3, Name = "Paul", City = "Chicago" });
this.EditingDataGrid.ItemsSource = Persons;
this.EditingDataGrid.CurrentCellChanged += new EventHandler<EventArgs>(EditingDataGrid_CurrentCellChanged);
}
void EditingDataGrid_CurrentCellChanged(object sender, EventArgs e)
{
DataGridCell Cell = GetCurrentCell(this.EditingDataGrid);
var Position = Cell.PointToScreen(new Point(0, 0));
// WHY X NEVER CHANGES??!!
MessageBox.Show("X=" + Position.X.ToString() + ", Y=" + Position.Y.ToString(), "Position");
}
/// <summary>
/// Returns, for this supplied Source Data-Grid, the current Data-Grid-Cell.
/// May return null if no associated Cell is found.
/// </summary>
public static DataGridCell GetCurrentCell(DataGrid SourceDataGrid)
{
if (SourceDataGrid.CurrentCell == null)
return null;
var RowContainer = SourceDataGrid.ItemContainerGenerator.ContainerFromItem(SourceDataGrid.CurrentCell.Item);
if (RowContainer == null)
return null;
var RowPresenter = GetVisualChild<System.Windows.Controls.Primitives.DataGridCellsPresenter>(RowContainer);
if (RowPresenter == null)
return null;
var Container = RowPresenter.ItemContainerGenerator.ContainerFromItem(SourceDataGrid.CurrentCell.Item);
var Cell = Container as DataGridCell;
// Try to get the cell if null, because maybe the cell is virtualized
if (Cell == null)
{
SourceDataGrid.ScrollIntoView(RowContainer, SourceDataGrid.CurrentCell.Column);
Container = RowPresenter.ItemContainerGenerator.ContainerFromItem(SourceDataGrid.CurrentCell.Item);
Cell = Container as DataGridCell;
}
return Cell;
}
/// <summary>
/// Returns the nearest child having the specified TRet type for the supplied Target.
/// </summary>
public static TRet GetVisualChild<TRet>(DependencyObject Target) where TRet : DependencyObject
{
if (Target == null)
return null;
for (int ChildIndex = 0; ChildIndex < VisualTreeHelper.GetChildrenCount(Target); ChildIndex++)
{
var Child = VisualTreeHelper.GetChild(Target, ChildIndex);
if (Child != null && Child is TRet)
return (TRet)Child;
else
{
TRet childOfChild = GetVisualChild<TRet>(Child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
The DataGrid is just defined by...
<DataGrid x:Name="EditingDataGrid"/>
Maybe exists there an alternative to get that DataGridCell position?
You can get the DataGridCell from CurrentCell like this
void EditingDataGrid_CurrentCellChanged(object sender, EventArgs e)
{
DataGridCell Cell = GetDataGridCell(EditingDataGrid.CurrentCell);
var Position = Cell.PointToScreen(new Point(0, 0));
MessageBox.Show("X=" + Position.X.ToString() + ", Y=" + Position.Y.ToString(), "Position");
}
public static DataGridCell GetDataGridCell(DataGridCellInfo cellInfo)
{
if (cellInfo.IsValid == false)
{
return null;
}
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent == null)
{
return null;
}
return cellContent.Parent as DataGridCell;
}
You could also create an extension method on the DataGrid to do this
DataGridExtensions.cs
public static class DataGridExtensions
{
public static DataGridCell GetCurrentDataGridCell(this DataGrid dataGrid)
{
DataGridCellInfo cellInfo = dataGrid.CurrentCell;
if (cellInfo.IsValid == false)
{
return null;
}
var cellContent = cellInfo.Column.GetCellContent(cellInfo.Item);
if (cellContent == null)
{
return null;
}
return cellContent.Parent as DataGridCell;
}
}
Which you can use like this everytime you want to get the current DataGridCell
DataGridCell Cell = EditingDataGrid.GetCurrentDataGridCell();
I guess what's going on is that your grid's default selection mode is full row, the code you're using to get the DataGridCell is getting the first selected cell which holds the "Id" column value.
What you can try to do is changing the grid's selection mode to "Cell" and this trigger the message box with correct coordinates.
<DataGrid x:Name="EditingDataGrid" SelectionUnit="Cell"/>
Also I've changed your code a bit, see if it would work for you:
void EditingDataGrid_CurrentCellChanged(object sender, EventArgs e)
{
// this will iterate through all selected cell of the datagrid
foreach (DataGridCellInfo cellInfo in this.EditingDataGrid.SelectedCells)
{
DataGridCell Cell = GetCurrentCell(this.EditingDataGrid, cellInfo);
if (Cell != null)
{
var Position = Cell.PointToScreen(new Point(0, 0));
MessageBox.Show("X=" + Position.X.ToString() +
", Y=" + Position.Y.ToString() +
" Content = " + ((TextBlock)Cell.Content).Text.ToString(), "Position");
}
}
}
/// <summary>
/// Returns, for this supplied Source Data-Grid, the current Data-Grid-Cell.
/// May return null if no associated Cell is found.
/// </summary>
public static DataGridCell GetCurrentCell(DataGrid grid, DataGridCellInfo cellInfo)
{
DataGridCell result = null;
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(cellInfo.Item);
if (row != null)
{
int columnIndex = grid.Columns.IndexOf(cellInfo.Column);
if (columnIndex > -1)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);
result = presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex) as DataGridCell;
}
}
return result;
}
/// <summary>
/// Returns the nearest child having the specified TRet type for the supplied Target.
/// </summary>
static T GetVisualChild<T>(Visual 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;
}
hope this helps, regards

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