How to prevent WPF edit box scrolling to end on a programmatic SelectAll?
I have a TextBox that does a SelectAll() when it gets focus. Works great. The problem is that if the contents don't fit in the box, th开发者_StackOverflowen the SelectAll results in the contents getting scrolled to the end. I want it to scroll to the front instead - this box is being used with number fields.
My question is: is it possible to SelectAll() but avoid the scrolling?
The workaround I have right now is to queue up a ScrollToHome. I'm not liking this too much because it sometimes causes a pop as the text renders in one position then another.
public new void SelectAll()
{
base.SelectAll();
// changing selection does something with a queued scroll request, so have to queue ours in the back. this still may cause a flash
// if a render slips through. would like to find a better way to do this.
Dispatcher.BeginInvoke(DispatcherPriority.Background, ScrollToHome);
}
Note that the ScrollToHome has to be queued like this or it gets "overwritten" due to some other queued event..I'm guessing a notify-changed event internal to the textbox system. I poked around in the source and it's pretty complicated, had trouble figuring out exactly where it is doing its request to scroll.
So what I'm looking for is one of the following:
- How to SelectAll() without any scrolling occurring or with it scrolling to the front.
- How to force an update to the display so that I don't get the popping.
Wrap your SelectAll()
and with ScrollToHome()
calls with BeginChange()
and EndChange()
. This will save it from repainting:
public new void SelectAll()
{
BeginChange();
SelectAll();
ScrollToHome();
EndChange();
}
Another option is to lose focus off your TextBox
, then do the selection.
I had the exact same problem (textbox that selects all when it gets focus and I want it to scroll to the beginning and not the end), but in silverlight. There's no BeginChange/EndChange there, so I basically had come up with Scott Bilas's answer as well.
This means I get the repaint seen, which is frustrating, but is the best I can come up with for now. To make it even hackier, just a BeginInvoke is not enough, as the auto scroll to the bottom is queued and sometimes happens after the BeginInvoke action, so I have to introduce an artificial delay (spawn in new thread and sleep for 10ms before the BeginInvoke) to get it more reliable. So I ended up with this:
var scrollViewer = VisualTreeWalker.FindVisualChildren<ScrollViewer>(TextBox)
.FirstOrDefault();
new Thread(delegate()
{
Thread.Sleep(10);
scrollViewer.Dispatcher.BeginInvoke(() =>
{
scrollViewer.ScrollToLeft();
scrollViewer.ScrollToTop();
});
}).Start();
I played with doing a dependency property watcher to watch the HorizontalOffset of the ScrollViewer in the textbox, and then doing the BeginInvoke when the HorizontalOffset became non-zero, but this just required too many special cases.. what if there wasn't enough text in the textbox to cause autoscrolling so the HorizontalOffset stayed at 0? Then a legit user edit that should cause scrolling would get scrolled back to the top/left. It just turned into a big rabbit hole so I had to be content with this awful hack.
精彩评论