问题
I have created a WPF Control that will allow users of the application I'm developing to easily select a SQL Server in which to connect. On the control, there are three different categories of SQL Servers: Local, Recent, and More Servers on Network.
Local: The SQL Server instance on the machine in which the application is running.
Recent: SQL Servers instances the user has connected to recently.
More Servers on Network: SQL Server instances that are discovered by sending out a search request on the local network. The "More Servers on Network" is not automatically populated since it sometimes takes a while to populate. Most of the time, the users connect to recently used SQL Server instance, and don't need to browse the network.
In short, this control consists of several templated ListBox and TextBlock controls all nested inside of a main WPF ScrollViewer control. The ScrollViewer will grow to whatever size necessary to fit all the content, and the ScrollViewer will allow the user to scroll to any part of that content. Since the XAML for the entire control is quite lengthy, I have compressed it into pseudo code to get the point across:
<Grid>
<ScrollViewer x:name="mainScrollViewer">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
...
<Grid.RowDefinitions />
<Textblock Text="Local" />
<ListBox />
</Grid>
<Grid>
...
<Textblock Text="Recent" />
<ListBox />
</Grid>
...
<StackPanel>
</ScrollViewer>
</Grid>
Out of the gates, the control looks like this when it is launched:
If the user needs wants to browse other SQL Server instances out on the work, the user will click the "Click here to Search the local network" button. Clicking this button will spawn off a separate thread that will perform the searching, while keeping the UI responsive. During this time, the control display an animated spinning "wait" gif, and will look like this:
When the control has completed searching, the user will be able to click on any of the discovered servers, just like in the "Local" and "Recent" sections:
Depending on local network circumstances and other conditions however, the search does not always find all the available servers. When this happens, the user can click the hyperlink button: "Manually Add Server to this list". When this hyperlink button is clicked, a DataTrigger collapses the hyperlink and puts a TextBox up in its place for the user to manually enter the server name:
The textbox appears, and allows the user to enter the user name. There is an handler for the "KeyDown" event on the textbox that listens for the ENTER key. When the ENTER key is pressed, the server is added to the list, the textbox is collapsed, and the "ScrollToBottom()" method on the main ScrollViewer control is called and the newly added item is selected.
The following is the code-behind for the KeyDown handler:
private void txtAddServer_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
this.AddServerToListIsShown = false;
}
else if (e.Key == Key.Enter)
{
if (this.MoreServers == null)
MoreServers = new ObservableCollection<DatabaseServer>();
if (this.AllSQLServerNames == null)
this.AllSQLServerNames = new ObservableCollection<string>();
if (!this.MoreServers.Any(x => string.Compare(txtAddServer.Text, x.Description, true) == 0))
{
this.AllSQLServerNames.Add(this.txtAddServer.Text.ToUpper());
this.MoreServers.Add(new DatabaseServer { Description = this.txtAddServer.Text.ToUpper(), ServerName = this.txtAddServer.Text.ToUpper() });
var foundItem = this.MoreServers.FirstOrDefault(x => string.Compare(x.Description, this.txtAddServer.Text, true) == 0);
if (foundItem != null)
{
this.SelectedServer = foundItem;
}
this.AddServerToListIsShown = false;
mainScrollViewer.ScrollToBottom();
}
}
}
The problem:
The problem is, when a server name is added manually, the ScrollViewer usually don't scroll all the way to the bottom as it should. Depending on the size of the control and how many servers are in the list, it usually scrolls somewhere in the middle or toward the bottom (but not all the way), like so:
Why doesn't the ScrollViewer scroll all the way to the bottom???
回答1:
In thinking through the problem, I tried a few different things to gain a better understanding as to why the ScrollViewer wasn't scrolling to the bottom as it should. The first thing I did was add a test button with the content of "Scroll" that would call the "ScrollToBotton()" method of the scroll viewer. When I did this, the ScrollViewer scrolled to the bottom perfectly every time I tried it. It then occurred to me that the Dispatcher was still in the process of completing other UI operations at the same time "ScrollViewer.ScrollToBottom()" was being called. I changed the code in my KeyDown handler that calls the "ScrollToBottom()" to the following:
Dispatcher.Invoke(new Action(() =>
{
mainScrollViewer.ScrollToBottom();
}), DispatcherPriority.ContextIdle, null);
}
}
}
This instructs the Dispatcher to finish up all background priority items in its queue before attempting to perform the ScrollToBottom(), which causes my ScrollViewer to scroll correctly every time.
来源:https://stackoverflow.com/questions/37847802/scrollviewer-scrolltobottom-not-completely-scrolling