UserControl BringIntoView() not working properly

旧城冷巷雨未停 提交于 2019-12-24 02:34:11

问题


Background: I have a usercontrol defined in a ScrollViewer along with a ContentControl, the ContentControl will be visible all the time, and within it there is a Button, when the button is clicked will set the usercontrol to Visible, and when the usercontrol shows (Visiblility="Visible") I want it to be scrolled into the view. I have

XAML

<ScrollViewer  VerticalScrollBarVisibility="Auto"  MaxHeight="465">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <ContentControl Content="{Binding MyOtherViewModel}"  Width="960" ></ContentControl>
    <local:MyView  IsVisibleChanged="MyView_IsVisibleChanged" Grid.Row="1" Visibility="{Binding IsNonCompliant, Converter={StaticResource BooltoVisible}, UpdateSourceTrigger=PropertyChanged}" />        
</ScrollViewer>

Code Behind

private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            (sender as Control).BringIntoView();        
        }

Problem: this is not working, or more precisely, my usercontrol scrolled into the view first then revert back to the bottom of the ScrollViewer in a blink.

Weird thing: show a messagebox before calling BringIntoView will correctly display my usercontrol into the middle of the view

Current hack solution: you can see this works even to close the Window immediately after its loaded

private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
            {
                Window ss = new Window();
                ss.Loaded += new RoutedEventHandler(ss_Loaded);
                ss.ShowDialog();
                (sender as Control).BringIntoView();        
            }   

private void ss_Loaded(object sender, RoutedEventArgs e)
        {
            (sender as Window).Close();
        }

Question: I know there must be something else going on, but I just can't identify it, but I really want to know what happened when a window showing with ShowDialog? Is this because it refreshes the window so that the BringIntoView will happen only after the usercontrol been loaded? (Not as the problem I have now: BringIntoView happened first, and then the window get refreshed and put the scrollbar back to the top). And what is the correct fix for my problem?


回答1:


It looks like BringIntoView called before my Usercontrol getting rendered, as a result when it gets fully rendered, the scrollbar is revert back to the top (as I have described in my question). And thanks for the answer from @Evgeny posted for another question, I get a better solution now (less hack maybe?). Still want to see if there are better solutions.

   private void MyView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
   {
        var border = (FrameworkElement)sender;
        if (border.IsVisible)
        {
            //Window ss = new Window();
            //ss.Loaded += new RoutedEventHandler(ss_Loaded);
            //ss.ShowDialog();
            using (BackgroundWorker bg = new BackgroundWorker())
            {
                bg.DoWork += new DoWorkEventHandler(bg_DoWork);
                bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
                Tuple<FrameworkElement, double> b = new Tuple<FrameworkElement, double>(border, border.Height);
                bg.RunWorkerAsync(b);
            }
        }
   }       

    private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        (e.Result as UserControl).BringIntoView();
    }

    private void bg_DoWork(object sender, DoWorkEventArgs e)
    {   
        int maxwait = 300 //not scrolled to the view is not a disaster, but if the program hangs forever it will be a disaster, so set this to prevent that from happening
        while (maxwait!=0 
               && 
               (e.Argument as Tuple<FrameworkElement, double>).Item1.ActualHeight != (e.Argument as Tuple<FrameworkElement, double>).Item2)
        {
            Thread.Sleep(1);
            maxwait --;
        }
        e.Result = (e.Argument as Tuple<FrameworkElement, double>).Item1;
    }  



回答2:


This is not correct to use background worker for such things! if you can use only LayoutUpdated event it will be the best option. Do job when actual width or height not equals to 0 or use timer instead of bg worker.

userControl.LayoutUpdated+=OnLayoutUpdated;

private bool loaded=false;
    private void OnLayoutUpdated(object sender,EventArgs e)
    {
      if (!loaded && (view.ActualHeight > 0 || view.ActualWidth > 0))
      {
         // Unsubscribe.
         userControl.LayoutUpdated-=OnLayoutUpdated;
         loaded =true;
      }
    }


来源:https://stackoverflow.com/questions/18724686/usercontrol-bringintoview-not-working-properly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!