2011-12-03 25 views
32

我有一個TextBlock其內容是綁定到ViewModel的字符串屬性的數據。這TextBlock有一個ScrollViewer纏繞它。如何使用Xaml自動滾動到ScrollViewer的底部並進行綁定?

我想要做的是每次日誌更改時,ScrollViewer將滾動到底部。理想我想是這樣的:

<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto" 
        ScrollPosition="{Binding Path=ScrollPosition}"> 
     <TextBlock Text="{Binding Path=Logs}"/> 
    </ScrollViewer> 

想背後使用代碼!我正在尋找的解決方案應該使用僅限綁定和/或Xaml。

+2

沒有代碼背後的任何具體原因? –

+6

我猜這是一個宗教信仰,背後的代碼和MVVM不應該混合。 –

+3

你是對的,但在我看來,MVVM只建議你的業務邏輯(視圖模型)不應該與你的用戶界面(視圖)混合。滾動查看器是用戶界面/視圖,如果我們在後面的代碼中放置一些代碼將ScrollViewer移動到底部,它不會反對MVVM,因爲我們只是玩UI –

回答

42

您可以創建附加屬性或行爲來實現您想要的內容,而無需使用代碼。無論哪種方式,你仍然需要編寫一些代碼。

以下是使用附加屬性的示例。

附加屬性

public static class Helper 
{ 
    public static bool GetAutoScroll(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollProperty); 
    } 

    public static void SetAutoScroll(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollProperty, value); 
    } 

    public static readonly DependencyProperty AutoScrollProperty = 
     DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(Helper), new PropertyMetadata(false, AutoScrollPropertyChanged)); 

    private static void AutoScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var scrollViewer = d as ScrollViewer; 

     if (scrollViewer != null && (bool)e.NewValue) 
     { 
      scrollViewer.ScrollToBottom(); 
     } 
    } 
} 

XAML綁定

<ScrollViewer local:Helper.AutoScroll="{Binding IsLogsChangedPropertyInViewModel}" .../> 

您將需要創建一個布爾屬性IsLogsChangedPropertyInViewModel當字符串屬性更改其設置爲true。

希望這會有所幫助! :)

+0

不幸的是,我只是無法使用VS 2012 + MVVM Light在我的機器上工作。我的猜測是,它可能取決於沒有記錄的參考。我已經發布了傑夫博客的答案,這對我有效。 – Contango

+1

Julien XL的答案完全適合我。我有VS 2012 + Mahapps。我不使用MVVM Light – Zougi

+1

@Zougi它適用於我,VS2015 + MVVM Light。 Gread解決方案! – LueTm

5

這是很容易,例:

yourContronInside.ScrollOwner.ScrollToEnd(); 
yourContronInside.ScrollOwner.ScrollToBottom(); 
+1

考慮擴展您的答案以向提問者_why_解釋,這可以實現所需的結果,可能鏈接到文檔。如此,這僅僅是有用的。 –

+1

這對我來說似乎不是MVVM解決方案。技巧是在不使用代碼的情況下做到這一點.. – ecth

10

Geoff's Blog on ScrollViewer AutoScroll Behavior

加入這個類:

namespace MyAttachedBehaviors 
{ 
    /// <summary> 
    ///  Intent: Behavior which means a scrollviewer will always scroll down to the bottom. 
    /// </summary> 
    public class AutoScrollBehavior : Behavior<ScrollViewer> 
    { 
     private double _height = 0.0d; 
     private ScrollViewer _scrollViewer = null; 

     protected override void OnAttached() 
     { 
      base.OnAttached(); 

      this._scrollViewer = base.AssociatedObject; 
      this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated); 
     } 

     private void _scrollViewer_LayoutUpdated(object sender, EventArgs e) 
     { 
      if (Math.Abs(this._scrollViewer.ExtentHeight - _height) > 1) 
      { 
       this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight); 
       this._height = this._scrollViewer.ExtentHeight; 
      } 
     } 

     protected override void OnDetaching() 
     { 
      base.OnDetaching(); 

      if (this._scrollViewer != null) 
      { 
       this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated); 
      } 
     } 
    } 
} 

此代碼取決於混合行爲,需要以System.Windows.Interactivity參考。見help on adding System.Windows.Interactivity

如果您安裝了MVVM光NuGet包,你可以在這裏添加一個參考:

packages\MvvmLightLibs.4.2.30.0\lib\net45\System.Windows.Interactivity.dll 

確保你有你的頭,它指向System.Windows.Interactivity.dll這個屬性:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 

添加混合行爲成ScrollViewer

<i:Interaction.Behaviors> 
    <implementation:AutoScrollBehavior /> 
</i:Interaction.Behaviors> 

例如:

<GroupBox Grid.Row="2" Header ="Log"> 
    <ScrollViewer> 
     <i:Interaction.Behaviors> 
      <implementation:AutoScrollBehavior /> 
     </i:Interaction.Behaviors> 
     <TextBlock Margin="10" Text="{Binding Path=LogText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap"/> 
    </ScrollViewer> 
</GroupBox> 

我們必須添加一個定義命名空間,否則將不知道在哪裏找到我們剛纔添加的C#類。將此屬性添加到<Window>標記中。如果您使用的是ReSharper,它會自動爲您推薦。

xmlns:implementation="clr-namespace:MyAttachedBehaviors" 

現在,如果一切順利,框中的文本將始終向下滾動到底部。

給出的示例XAML會將綁定屬性LogText的內容打印到屏幕上,這非常適合記錄。

+0

如果它幫助任何人使用您的示例,您可以從Visual Studio 2012或2013安裝文件夾中獲取.NET 4.5的System.Windows.Interactivity.dll,如果您安裝了Blend這些版本自Blend自帶。 –

+0

@亞歷克斯馬歇爾。您絕對正確,謝謝您添加此筆記。當我使用MVVM Light時,我無法使用MVVM Light提供的確切'System.Windows.Interactivity.dll'(正如答案中所述)。如果使用其他MVVM框架,甚至是代碼背後,那麼這可能會工作得很好。換句話說,如果你的MVVM框架已經包含了,你不能在你的項目中添加這個'.dll'的多個版本。 – Contango

+0

您可以添加設置資源的XAML部分嗎? –

15

回覆更新2017-12-13,現在使用ScrollChanged事件並檢查範圍的大小是否更改。更可靠,更不具有手動滾動干擾

我知道這個問題是舊的,但我有一個改進的實現:

  • 沒有外部依賴
  • 你只需要設置屬性一旦

的代碼在很大程度上受這兩個賈斯汀XL的影響,升貼水的解決方案

public static class AutoScrollBehavior 
{ 
    public static readonly DependencyProperty AutoScrollProperty = 
     DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollBehavior), new PropertyMetadata(false, AutoScrollPropertyChanged)); 


    public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
    { 
     var scrollViewer = obj as ScrollViewer; 
     if(scrollViewer != null && (bool)args.NewValue) 
     { 
      scrollViewer.SizeChanged += ScrollViewer_ScrollChanged; 
      scrollViewer.ScrollToEnd(); 
     } 
     else 
     { 
      scrollViewer.ScrollChanged-= ScrollViewer_ScrollChanged; 
     } 
    } 

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
    { 
     // Only scroll to bottom when the extent changed. Otherwise you can't scroll up 
     if (e.ExtentHeightChange != 0) 
     { 
      var scrollViewer = sender as ScrollViewer; 
      scrollViewer?.ScrollToBottom(); 
     } 
    } 

    public static bool GetAutoScroll(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(AutoScrollProperty); 
    } 

    public static void SetAutoScroll(DependencyObject obj, bool value) 
    { 
     obj.SetValue(AutoScrollProperty, value); 
    } 
} 

用法:

<ScrollViewer n:AutoScrollBehavior.AutoScroll="True" > // Where n is the XML namespace 
+0

如果將此屬性放在任何不是ScrollViewer的東西上,我會看到一個空引用異常等待發生。 –

+0

究竟在哪裏?我在使用Elvis運算符和'as'投射我期待ScrollViewer的情況。 –

+2

@RoyT在'AutoScrollPropertyChanged'方法中,'scrollViewer'爲null時,不僅'scrollViewer'不是'null','NewValue'是'false',可以到達'else' – cgijbels

-1

我用@Roy T.的答案,但是我想加入的規定,如果你滾動往回趕,但後來添加的文本,滾動視圖應該自動滾動到下。在地方SizeChanged事件的

private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
{ 
    var scrollViewer = sender as ScrollViewer; 

    if (e.ExtentHeightChange > 0) 
    { 
     scrollViewer.ScrollToEnd(); 
    }  
} 

我用這個。

+0

我把它作爲我XAML中的一個行爲來綁定。像這樣: '' – cabusto

0

這是一個細微的變化。

當滾動查看器高度(視口)和滾動演示者的內容(範圍)更改高度時,這將滾動到底部。

這是基於羅伊T的答案,但我無法評論,所以我已發佈爲答案。

public static class AutoScrollHelper 
    { 
     public static readonly DependencyProperty AutoScrollProperty = 
      DependencyProperty.RegisterAttached("AutoScroll", typeof(bool), typeof(AutoScrollHelper), new PropertyMetadata(false, AutoScrollPropertyChanged)); 


     public static void AutoScrollPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
     { 
      var scrollViewer = obj as ScrollViewer; 
      if (scrollViewer == null) return; 

      if ((bool) args.NewValue) 
      { 
       scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; 
       scrollViewer.ScrollToEnd(); 
      } 
      else 
      { 
       scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; 
      } 
     } 

     static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 
     { 
      // Remove "|| e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0" if you want it to only scroll to the bottom when it increases in size 
      if (e.ViewportHeightChange > 0 || e.ExtentHeightChange > 0 || e.ViewportHeightChange < 0 || e.ExtentHeightChange < 0) 
      { 
       var scrollViewer = sender as ScrollViewer; 
       scrollViewer?.ScrollToEnd(); 
      } 
     } 

     public static bool GetAutoScroll(DependencyObject obj) 
     { 
      return (bool) obj.GetValue(AutoScrollProperty); 
     } 

     public static void SetAutoScroll(DependencyObject obj, bool value) 
     { 
      obj.SetValue(AutoScrollProperty, value); 
     } 
    }