WPF DataGrid - Retain selection when disabling - c#

I have been struggling with this for a while now. I have a Master / Details layout in my application, and am faced, like many others, with the problem of the DataGrid loosing its selection when disabling it. Essencialy, after selecting an element from the list to populate a series of fields, the user presses "Edit", wich disables the DataGrid and enables all of the form's fields. Pressing the "Save" button will revert these actions after saving the data... Pretty strait forward.
I am on Windows 7 developping with VS 2010 in the .Net Framework 4.
What I have tried:
1) Based on this post, I have tried to use the DataGrid in the June 2009 version of the WPF Toolkit, but I had the same reaction.
2) Based on this WPF CodePlex bug report, I have tried to create a custom control based on the DataGrid and to override the OnIsEnabledChanged call to remove the call to "UnselectAllCells", but with no code example, I can't even get it to fire once. I have tried:
public class FormMainDataGrid : DataGrid
{
static FormMainDataGrid()
{
IsEnabledProperty.OverrideMetadata(typeof(FormMainDataGrid), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEnabledChanged)));
}
public FormMainDataGrid() : base() { }
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this was added in new version !!!
/*
if (!(bool)(e.NewValue))
{
((DataGrid)d).UnselectAllCells();
}
*/
// Many commands use IsEnabled to determine if they are enabled or not
CommandManager.InvalidateRequerySuggested();
}
}
but this still unselects the currently selected row as soon as I disable the DataGrid. I have tried to interprete the last comments (in the Codeplex bug report) like this:
public class FormMainDataGrid : DataGrid
{
static FormMainDataGrid()
{
}
public static void OverrideStuff()
{
IsEnabledProperty.OverrideMetadata(typeof(FormMainDataGrid), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEnabledChanged)));
}
public FormMainDataGrid() : base() { }
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this was added in new version !!!
/*
if (!(bool)(e.NewValue))
{
((DataGrid)d).UnselectAllCells();
}
*/
// Many commands use IsEnabled to determine if they are enabled or not
CommandManager.InvalidateRequerySuggested();
}
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
FormMainDataGrid.OverrideStuff();
base.OnStartup(e);
}
}
but that does not even fire the modified version of the method.
First, am-I going the right way for this? Considering that the Deselection is caused by this method, can I completely replace the internal call to 'OnIsEnabledChanged' for my own method?
Is there another way I could be tackling this problem?
Or more specificly, how can i stop the call to the base version of this method since it is not an override, thus I cannot 'not' call the base.OnIsEnabledChanged?
Thanks alot!

For future reference, if anyone is running into the same issue.
Re-setting the SelectedValue has a lot of side-effects.
This is the correct way to override the metadata on the Grid:
public class MyDataGrid : DataGrid
{
static MyDataGrid()
{
IsEnabledProperty.OverrideMetadata(typeof(MyDataGrid), new CustomFrameworkPropertyMetadata(OnIsEnabledChanged));
}
/// <summary>
/// Fixes the issue that the DataGrid's selection is cleared whenever the DataGrid is disabled.
/// Tricky: this issue only happens for 4.0 installations, it is fixed in 4.5 (in-place upgrade) installations.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(CanUserAddRowsProperty);
d.CoerceValue(CanUserDeleteRowsProperty);
//this is there in 4.0 dlls, not in the in-place upgrade 4.5 dlls.
//if (!(bool)(e.NewValue))
//{
// ((DataGrid)d).UnselectAllCells();
//}
CommandManager.InvalidateRequerySuggested();
}
class CustomFrameworkPropertyMetadata : FrameworkPropertyMetadata
{
public CustomFrameworkPropertyMetadata(PropertyChangedCallback propertyChangedCallback)
: base(propertyChangedCallback)
{
}
protected override void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
// See: http://msdn.microsoft.com/en-us/library/system.windows.propertymetadata.merge.aspx
// See: http://msdn.microsoft.com/en-us/library/ms751554.aspx
// By default, PropertyChangedCallbacks are merged from all owners in the inheritance hierarchy,
// so all callbacks are called whenever the property changes.
var thisPropertyChangedCallback = this.PropertyChangedCallback;
base.Merge(baseMetadata, dp);
// We do NOT want that default behavior here;
// The callback of DataGrid should not be called here - it clears the selection, we don't want that.
// But the callback of UIElement should be called here - it visually disabled the element, we still want that.
if (baseMetadata.PropertyChangedCallback != null)
{
Delegate[] invocationList = baseMetadata.PropertyChangedCallback.GetInvocationList();
PropertyChangedCallback inheritedPropertyChangedCallback = null;
foreach (var invocation in invocationList)
{
if (invocation.Method.DeclaringType == typeof(DataGrid))
{
// Do nothing; don't want the callback from DataGrid that clears the selection.
}
else
{
inheritedPropertyChangedCallback = inheritedPropertyChangedCallback == null
? (PropertyChangedCallback)invocation
: (PropertyChangedCallback)Delegate.Combine(inheritedPropertyChangedCallback, invocation);
}
}
this.PropertyChangedCallback = thisPropertyChangedCallback != null
? (PropertyChangedCallback)Delegate.Combine(inheritedPropertyChangedCallback, thisPropertyChangedCallback)
: inheritedPropertyChangedCallback;
}
}
}
}
Note that the issue mentioned in this post only happens in 4.0 installations without 4.5 installed.
It is 'fixed' in .net 4.5, even for applications targeting 4.0
(the "4.5 is an in-place upgrade" scenario / misery).
Regards,
Koen

I generally don't disable controls specifically for this reason. I have found it much better to either collapse the control which keeps its databinding current, or if I must keep it visible but disallow any kind of interaction, put a partially transparent black border over it that is normally collapsed and becomes visible on command.

The same problem with the Up-Down key still exists with IsHitTestVisible = false.
So what I ended up doing is re-working the custom control like this:
public class FormMainDataGrid : DataGrid
{
public FormMainDataGrid() : base() {
this.IsEnabledChanged += new DependencyPropertyChangedEventHandler(DataGrid_IsEnabledChanged);
this.SelectionChanged += new SelectionChangedEventHandler(DataGrid_SelectionChanged);
}
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
if (this.IsEnabled)
{
_selectedValue = this.SelectedValue;
}
}
private object _selectedValue;
private void DataGrid_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
this.SelectedValue = _selectedValue;
}), null);
}
}
This works pretty well... I just have to be carefull because changing the SelectedValue when the control is disable will then put it off track...
So in conclusion, I believe that your solution is the most complete, but mine allows me to keep my form's code as lean & mean as possible.
Thanks for your help!

Related

Design pattern to accommodate two different ways of handling a Winform's functions

I have a WinForm that I have used to build and test an email newsletter. This form contains a number of methods and events.
I am now adding a new feature to my program to allow split testing (A/X Test) and therefore creating up to 4 different newsletters for a campaign.
Therefore I'd like to extend my form to accommodate both normal newsletters and A/X newsletters. I'm going to add two different modes to my form. Something like:
private enum CampaignMode { Normal, AxTest };
They will be very similar in appearance, except a number of controls' visibility will change.
Apart from that, almost all methods and events will have two separate ways of handling.
What design pattern should I use so that I don't have to create a new separate form?
For simplicity, let's say my form has the following methods:
Constructor: probably receiving the EditMode as a parameter
Load
Create: Button click event
SetControlViews: Based on EditMode set the visibility of controls
MethodA: Specific to Normal mode
MethodB: Specific to AxTest mode
Sounds like Template Method would be a good candidate.
Make a base class which takes care of logic for the basic initialization, Load(), Create(), SetControlViews() and then make Method() virtual and override in two derived classes according to specialized logic. Also, the constructor of each derived class could do some specialized initialization.
This way you don't even need a CampaignMode enum (or EditMode if you meant those to be identical). The existence of different entities there is illustrated by the existence of different derived classes.
I would simply have pairs of functions for each method/event and then have the main handler call the appropriate one, depending on the value of CampaignMode.
public enum CampaignMode { Normal, AxTest };
public partial class DemoForm : Form
{
private CampaignMode campaignMode;
public DemoForm(CampaignMode mode)
{
InitializeComponent();
campaignMode = mode;
SetControlsVisibility();
}
private void SetControlsVisibility()
{
if (campaignMode == CampaignMode.Normal)
{
//Set normal controls visible;
//Set axtest controls invisible;
}
else
{
//Set normal controls invisible;
//Set axtest controls visible;
}
}
private void button1_Click(object sender, EventArgs e)
{
if (campaignMode == CampaignMode.Normal)
{
MethodA();
}
else
{
MethodB();
}
}
private void MethodA()
{
}
private void MethodB()
{
}
}
Note: if there are many controls, then it is neater to have a visibility function with a bool parameter:eg
private void SetNormalVisibility(bool isNormal)
{
//normalTextBox.Visible = isNormal;
//normalButton.Visible = isNormal;
//axTestTextBox.Visible = !isNormal;
//axTestButton.Visible = !isNormal;
}
In which case, change the SetControlsVisibility function to:
private void SetControlsVisibility()
{
if (campaignMode == CampaignMode.Normal)
{
SetNormalVisibility(true);
}
else
{
SetNormalVisibility(false);
}
}
HTH
Jonathan

I want change the small check-box beside to item color in checkedlistbox in c#

I want change the small check-box color beside to item color in checklist in c#
public ColorControl()
{
KnownColor[] colors = Enum.GetValues(typeof(KnownColor)) as KnownColor[];
foreach (var item in colors)
{
this.Items.Add(item.ToString());
}
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
Color ItemColor = Color.FromName(this.Items[e.Index].ToString());
e.Graphics.DrawString(this.Items[e.Index].ToString(),this.Font,new SolidBrush(ItemColor) ,e.Bounds);
base.OnDrawItem(e);}
Not sure if this is worthy of an answer but here is a shot.
As is mentioned in the answer by Cody Grey The checkbox is not really simple to override. Another alternative besides making your own class inheriting from CheckListBox is to make your own control inheriting from Control. You can use buttons with changing background colors for the checkboxes. And add whatever other features you want.
Something like
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace CustomButton
{
public partial class NeatButton : Control
{
//Some globals
private bool _Pressed = false;
private bool _Activated = false;
//you will want to put your code for clicking checkboxes in the Mouse overrides. The OnPaint override is where you decide how the boxes look.
protected override void OnMouseDown(MouseEventArgs e){...}
protected override void OnMouseUp(MouseEventArgs e){...}
protected override void OnPaint(PaintEventArgs pe){...}
//You will want some propeties
public new string Text
{
get { return base.Text; }
set
{
if (value == base.Text)
return;
base.Text = value;
Invalidate(); //Keeps text showing changes in real time
}
}
/// <summary>
/// Works with Pressed to determine if the button should do something when clicked. **Use a property like this for the checkboxes**
/// </summary>
private bool Activated
{
get { return _Activated; }
set
{
if (value == _Activated)
return;
_Activated = value;
Invalidate();
}
}
/// <summary>
/// Works with Activated to determine if the button should do something when clicked
/// </summary>
private bool Pressed
{
get { return _Pressed; }
set
{
if (value == _Pressed)
return;
_Pressed = value;
Invalidate();
}
}
While this is not a complete example it should show a very basic set up for a custom control.
Once the custom control is working then want to build it and grab the exe from where ever you built it. Then in the form you want to use it in you drop the exe in to the project folder. Then in the solution explorer right click on References and then Add reference. Then browes to your exe and check it off and click 'OK'. Save your project and open up the form designer. Right click on the ToolBox and click Choose Items. Find your exe and check it off and hit ok. The Checkbox you made should now appear in the tool box and can be dragged and dropped onto a form and used like any other control.
Good luck.
The checkbox is drawn by the operating system. It is intentionally designed so that it looks like every other checkbox on the screen. This massively helps with usability. Users will not know what they can click on a red box and use it like a checkbox.
If you insist on making your application difficult to use, you will have to owner-draw the control. It will not be easy, CheckedListBox is not designed to support owner-drawing. The DrawItem event is documented as being "not relevant to this class." The control is already owner-drawn by the framework.
At the very least, you will need to create a new class that inherits from CheckedListBox so that you can override the OnDrawItem method.
public class ColorCheckBoxListBox : CheckedListBox
{
protected override void OnDrawItem(DrawItemEventArgs e)
{
// ...
}
}

Custom copy/paste behaviour with C# winforms whilst allowing text edit copy/paste

I currently have a C# winforms app with Ctrl+C and Ctrl+V bound as keyboard shortcuts on the Edit main menu. There is some custom copy/paste behaviour inside code which responds to these menu items, such as copying and pasting rows in listviews.
However, my problem is that you can edit text inside a row, and when you do so, I want Ctrl+C and Ctrl+V to not trigger the edit menu command and should rather default to the normal text based copy/paste.
One thing I tried is triggering BeforeLabelEdit and AfterLabelEdit events, and manually disabling/re-enabling the menu items from inside there. Unfortunately, it seems that a keyboard shortcut on a disabled menu item still triggers the menu_Popup event, which is currently used to decide which menu items should be enabled/disabled. (For example, "Paste" is only active if there is text on the clipboard). So even if I disable the menu item, the keyboard shortcut will still activate the Popup event, which will re-enable the menu item. (Is this a bug?)
I can't find any method of temporarily disabling a menu items keyboard shortcut without manually storing the old shortcut, setting the shortcut to null, then copying it back when I need to re-enable (which feels dirty).
Surely overriding the copy/paste behaviour, or adding to it, is a common thing to want to do? Is there a better pattern to use here?
It looks like you'll need to use something lower level than C# offers if you want to override the default copy/paste behavior (see Clipboard event C# and Detect & Differentiate Clipboard Events (Cut,Copy and Paste)). However, perhaps you could put your logic behind a "guard" that knows how to direct a request for action (like the "Copy") and then redirect it as approprite.
Here is a sample class:
namespace Your.App
{
public class GuardedCommand
{
public bool CurrentlyEditing { get; set; }
public GuardedCommand()
{
CurrentlyEditing = false;
}
public void DoCopy()
{
if(CurrentlyEditing)
StandardCopyCommand();
else
ShortcutCopyCommand();
}
void ShortcutCopyCommand() { /*menu work here (or delegate to another class)*/ }
void StandardCopyCommand() { /*"normal" work here (or delegate again)*/ }
}
}
To use you would create the class, then set it's guardedCommand.CurrentlyEditing property as appropriate in the BeforeLabelEdit and AfterLabelEdit events. Then, whereever you catch the CTRL+C shortcut, just invoke guardedCommand.DoCopy() and it will take care of the rest.
If you're looking to read about a pattern for what you're looking to do, then check out the State Pattern, which the above code is sort of and implementation of. To be a real State (or Strategy) Pattern it would need to have seperate concrete classes that implement DoCopy() and DoPaste() rather than just using an if/else or switch. Then, when CurrentlyEditing is being changed, the proper concrete implementation would be set as the current class used to handle the DoCopy() method.
And just because it took me longer to write that paragraph than it would have to just give you some code, here's some code:
namespace Your.App
{
//correct implementation of the State Pattern
interface IClipboard
{
void Copy();
void Paste();
}
class MyCustomClipboard : IClipboard
{
public void Copy() { /*your special code*/ }
public void Paste() { /*your code again*/ }
}
class DefaultClipboard : IClipboard
{
public void Copy() { /*default code*/ }
public void Paste() { /*default code again*/ }
}
public class StateClass
{
IClipboard State { get; set; }
public StateClass()
{
CurrentlyEditing = false;
}
bool _currentlyEditing;
public bool CurrentlyEditing
{
get { return _currentlyEditing; }
set
{
_currentlyEditing = value;
if(_currentlyEditing)
State = new DefaultClipboard();
else
State = new MyCustomClipboard();
}
}
public void Copy()
{
State.Copy();
}
public void Paste()
{
State.Paste();
}
}
}
As you can see, this may be a bit overkill when there are only two states (and the number of states probably won't increase).

how UI language is changed?

Here, I have a bit confusion about UI language. If language is changed then what happens? The whole folder gets changed or Culture gets loaded? I cannot get what is actually happening.
Properties.Strings.MainWindow_Language_Selection_English_Label="English"
Properties.Strings.MainWindow_Language_Selection_Gujarati_Label="ગુજરાતી"
Please explain what is happening.
private void LanguageSelection_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem item = LanguageSelection.SelectedItem as ComboBoxItem;
if (item.Content.ToString() == Properties.Strings.MainWindow_Language_Selection_English_Label)
{
CultureManager.UICulture = new System.Globalization.CultureInfo("en");
}
else if (item.Content.ToString() == Properties.Strings.MainWindow_Language_Selection_Gujarati_Label)
{
CultureManager.UICulture = new System.Globalization.CultureInfo("gu");
}
Settings.Default["UILanguage"] = CultureManager.UICulture.Name;
Settings.Default.Save();
}
In general, setting the culture on application thread will be effective on the next form that is displayed, so to make this work you probably need a login/language selection window where you set the main thread's culture and then show application's main window.
There were a few attempts around this to make language selection take effect immadiately (easier in WPF) but this is how it works out of the box.
In WPF, however, if you are directly binding UI elements to resources you can make the UI update by raising a property change event on your resource property. The easiest way to achieve this (other than creating a new code generator for the .resx file) would be to wrap your resources in a model class like this:
public class StringRes : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public string Login
{
get { return Properties.Strings.Login; }
}
public string Password
{
get { return Properties.Strings.Password; }
}
public void NotifyLanguageChanged()
{
PropertyChanged(this, new PropertyChangedEventArgs("Login"));
PropertyChanged(this, new PropertyChangedEventArgs("Password"));
}
}
public class MainWindow
{
private StringRes _resources;
private void LanguageSelection_SelectionChanged()
{
System.Threading.Thread.CurrentThread.CurrentUICulture = GetCurrentCulture();
_resources.NotifyLanguageChanged();
}
}
If you have bound your UI elements to the instance of the StringRes class, they will be updated when you raise the notification change event in your model.

Scroll WPF ListBox to the SelectedItem set in code in a view model

I have a XAML view with a list box:
<control:ListBoxScroll ItemSource="{Binding Path=FooCollection}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
ScrollSelectedItem="{Binding SelectedFoo}">
<!-- data templates, etc. -->
</control:ListBoxScroll>
The selected item is bound to a property in my view. When the user selects an item in the list box my SelectedFoo property in the view model gets updated. When I set the SelectedFoo property in my view model then the correct item is selected in the list box.
The problem is that if the SelectedFoo that is set in code is not currently visible I need to additionally call ScrollIntoView on the list box. Since my ListBox is inside a view and my logic is inside my view model ... I couldn't find a convenient way to do it. So I extended ListBoxScroll:
class ListBoxScroll : ListBox
{
public static readonly DependencyProperty ScrollSelectedItemProperty = DependencyProperty.Register(
"ScrollSelectedItem",
typeof(object),
typeof(ListBoxScroll),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onScrollSelectedChanged)));
public object ScrollSelectedItem
{
get { return (object)GetValue(ScrollSelectedItemProperty); }
set { SetValue(ScrollSelectedItemProperty, value); }
}
private static void onScrollSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listbox = d as ListBoxScroll;
listbox.ScrollIntoView(e.NewValue);
}
}
It basically exposes a new dependency property ScrollSelectedItem which I bind to the SelectedFoo property on my view model. I then hook into the property changed callback of the dependent property and scroll the newly selected item into view.
Does anyone else know of an easier way to call functions on user controls on a XAML view that is backed by a view model? It's a bit of a run around to:
create a dependent property
add a callback to the property changed callback
handle function invocation inside the static callback
It would be nice to put the logic right in the ScrollSelectedItem { set { method but the dependency framework seems to sneak around and manages to work without actually calling it.
Have you tried using Behavior... Here is a ScrollInViewBehavior. I have used it for ListView and DataGrid..... I thinks it should work for ListBox......
You have to add a reference to System.Windows.Interactivity to use Behavior<T> class
Behavior
public class ScrollIntoViewForListBox : Behavior<ListBox>
{
/// <summary>
/// When Beahvior is attached
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
/// <summary>
/// On Selection Changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AssociatedObject_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (sender is ListBox)
{
ListBox listBox = (sender as ListBox);
if (listBox .SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action) (() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=
null)
listBox.ScrollIntoView(
listBox.SelectedItem);
}));
}
}
}
/// <summary>
/// When behavior is detached
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -=
AssociatedObject_SelectionChanged;
}
}
Usage
Add alias to XAML as xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
then in your Control
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem,
Mode=TwoWay}"
SelectionMode="Single">
<i:Interaction.Behaviors>
<Behaviors:ScrollIntoViewForListBox />
</i:Interaction.Behaviors>
</ListBox>
Now When ever "MyItem" property is set in ViewModel the List will be scrolled when changes are refelected.
After reviewing the answers a common theme came up: external classes listening to the SelectionChanged event of the ListBox. That made me realize that the dependant property approach was overkill and I could just have the sub-class listen to itself:
class ListBoxScroll : ListBox
{
public ListBoxScroll() : base()
{
SelectionChanged += new SelectionChangedEventHandler(ListBoxScroll_SelectionChanged);
}
void ListBoxScroll_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView(SelectedItem);
}
}
I feel this is the simplest solution that does what I want.
Honourable mention goes to adcool2007 for bringing up Behaviours. Here are a couple of articles for those interested:
http://blogs.msdn.com/b/johngossman/archive/2008/05/07/the-attached-behavior-pattern.aspx
http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
I think for generic behaviours that will be added to several different user controls (e.g. click behaviours, drag behaviours, animation behaviours, etc.) then attached behaviours make a lot of sense. The reason I don't want to use them in this particular case is that the implementation of the behaviour (calling ScrollIntoView) isn't a generic action that can happen to any control other than a ListBox.
Because this is strictly a View problem, there's no reason you can't have an event handler in the code behind of your view for this purpose. Listen for ListBox.SelectionChanged and use that to scroll the newly selected item into view.
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ListBox)sender).ScrollIntoView(e.AddedItems[0]);
}
You also don't need a derived ListBox to do this. Just use a standard control and when the ListBox.SelectedItem value changes (as described in your original question), the above handler will be executed and the item will be scrolled into view.
<ListBox
ItemsSource="{Binding Path=FooCollection}"
SelectedItem="{Binding Path=SelectedFoo}"
SelectionChanged="ListBox_SelectionChanged"
/>
Another approach would be to write an attached property that listens for ICollectionView.CurrentChanged and then invokes ListBox.ScrollIntoView for the new current item. This is a more "reusable" approach if you need this functionality for several list boxes. You can find a good example here to get you started: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
I know this is an old question, but my recent search for the same problem has brought me to this. I wanted to use the behavior approach, but didn't want a dependency on the Blend SDK just to give me Behavior<T> so here's my solution without it:
public static class ListBoxBehavior
{
public static bool GetScrollSelectedIntoView(ListBox listBox)
{
return (bool)listBox.GetValue(ScrollSelectedIntoViewProperty);
}
public static void SetScrollSelectedIntoView(ListBox listBox, bool value)
{
listBox.SetValue(ScrollSelectedIntoViewProperty, value);
}
public static readonly DependencyProperty ScrollSelectedIntoViewProperty =
DependencyProperty.RegisterAttached("ScrollSelectedIntoView", typeof (bool), typeof (ListBoxBehavior),
new UIPropertyMetadata(false, OnScrollSelectedIntoViewChanged));
private static void OnScrollSelectedIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
if (selector == null) return;
if (e.NewValue is bool == false)
return;
if ((bool) e.NewValue)
{
selector.AddHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
else
{
selector.RemoveHandler(Selector.SelectionChangedEvent, new RoutedEventHandler(ListBoxSelectionChangedHandler));
}
}
private static void ListBoxSelectionChangedHandler(object sender, RoutedEventArgs e)
{
if (!(sender is ListBox)) return;
var listBox = (sender as ListBox);
if (listBox.SelectedItem != null)
{
listBox.Dispatcher.BeginInvoke(
(Action)(() =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem !=null)
listBox.ScrollIntoView(listBox.SelectedItem);
}));
}
}
}
and then usage is just
<ListBox ItemsSource="{Binding Path=MyList}"
SelectedItem="{Binding Path=MyItem, Mode=TwoWay}"
SelectionMode="Single"
behaviors:ListBoxBehavior.ScrollSelectedIntoView="True">
I'm using this (in my opinion) clear and easy solution
listView.SelectionChanged += (s, e) =>
listView.ScrollIntoView(listView.SelectedItem);
where listView is name of ListView control in xaml, SelectedItem is affected from my MVVM and code is inserted in constructor in xaml.cs file.
Try this:
private void lstBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lstBox.ScrollIntoView(lstBox.SelectedItem);
}
After tying various methods I found the following to be the simplest and the best
lstbox.Items.MoveCurrentToLast();
lstbox.ScrollIntoView(lstbox.Items.CurrentItem);
I took Ankesh's answer and made it not dependent on the blend sdk. The downside of my solution is that it will apply to all listboxes in your app. But the upside is no custom class needed.
When your app is initializing...
internal static void RegisterFrameworkExtensionEvents()
{
EventManager.RegisterClassHandler(typeof(ListBox), ListBox.SelectionChangedEvent, new RoutedEventHandler(ScrollToSelectedItem));
}
//avoid "async void" unless used in event handlers (or logical equivalent)
private static async void ScrollToSelectedItem(object sender, RoutedEventArgs e)
{
if (sender is ListBox)
{
var lb = sender as ListBox;
if (lb.SelectedItem != null)
{
await lb.Dispatcher.BeginInvoke((Action)delegate
{
lb.UpdateLayout();
if (lb.SelectedItem != null)
lb.ScrollIntoView(lb.SelectedItem);
});
}
}
}
This makes all of your listboxes scroll to selected (which I like as a default behavior).

Categories

Resources