天天看點

WPF自定義TextBox及ScrollViewer

原文: WPF自定義TextBox及ScrollViewer

  寒假過完,在家真心什麼都做不了,可能年齡大了,再想以前那樣能專心坐下來已經不行了。回來第一件事就是改了項目的一個bug,最近又新增了一個新的功能,為程式添加了一個消息欄。消息欄有許多形式,要求是一個不需要曆史記錄,可以用滑鼠選中消息内容的消息欄。我首先想到的就是TextBox,我個人比較喜歡美觀的,有點強迫症,是以必須把TextBox中的ScrollViewer給改寫了,好吧,開始。

  本博文分為三個部分,第一部分将描述如何改寫TextBox的布局,第二部分則描述如何改寫TextBox中的ScrollViewer樣式,第三部分則是對自定義樣式時産生的不明問題進行修補。

  一、生成自定義TextBox控件

  還是把這次寫的消息框做成使用者控件的形式,首先,前台簡單的XAML:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer
消息框基礎XAML

1 <TextBox x:Class="FS.PresentationManagement.Controls.MessageTextBox"
 2          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="SkyBlue">
 4     <TextBox.Template>
 5         <ControlTemplate TargetType="{x:Type TextBox}">
 6             <Grid Background="{TemplateBinding Background}">
 7                 <Grid.ColumnDefinitions>
 8                     <ColumnDefinition />
 9                     <ColumnDefinition Width="62" />
10                 </Grid.ColumnDefinitions>
11                 <!-- 文本框 -->
12                 <ScrollViewer x:Name="PART_ContentHost">
13                     <!-- 暫時省略 -->
14                 </ScrollViewer>
15                 <!-- 按鈕 -->
16                 <Button Name="BTN_Clear" Margin="5" Grid.Column="1" Click="BTN_Clear_Click">
17                     <Button.Template>
18                         <ControlTemplate>
19                             <Image Name="IMG_Clear" Source="../Pic/clear.png"/>
20                             <ControlTemplate.Triggers>
21                                 <Trigger Property="IsMouseOver" Value="True">
22                                     <Setter TargetName="IMG_Clear" Property="Source" Value="../Pic/clear2.png" />
23                                 </Trigger>
24                                 <Trigger Property="Button.IsPressed" Value="True">
25                                     <Setter TargetName="IMG_Clear" Property="Source" Value="../Pic/clear3.png" />
26                                 </Trigger>
27                             </ControlTemplate.Triggers>
28                         </ControlTemplate>
29                     </Button.Template>
30                 </Button>
31                 <Button Name="BTN_Close" Margin="0,-18,-25,0" VerticalAlignment="Top" Width="32" Height="32" Grid.Column="1" Click="BTN_Close_Click">
32                     <Button.Template>
33                         <ControlTemplate>
34                             <Image Name="IMG_Close" Source="../Pic/close.png" />
35                             <ControlTemplate.Triggers>
36                                 <Trigger Property="IsMouseOver" Value="True">
37                                     <Setter TargetName="IMG_Close" Property="Source" Value="../Pic/close2.png" />
38                                 </Trigger>
39                                 <Trigger Property="Button.IsPressed" Value="True">
40                                     <Setter TargetName="IMG_Close" Property="Source" Value="../Pic/close3.png" />
41                                 </Trigger>
42                             </ControlTemplate.Triggers>
43                         </ControlTemplate>
44                     </Button.Template>
45                 </Button>
46             </Grid>
47         </ControlTemplate>
48     </TextBox.Template>
49 </TextBox>      

  這個時候架構大概是,左邊将是一個ScrollViewer,用來顯示消息,右邊則是關閉和清理,兩個按鈕,至于按鈕的樣式,也已經進行了更改,每個按鈕使用三張圖檔來表示原始、停靠、按下三種狀态,需要注意,上面的XAML中按鈕的Source路徑是像“../Pic/xxx.png”,這是我把圖檔放到了目前檔案的--->上級目錄的--->Pic目錄下,是以實際上大家在使用的時候需要把這個屬性改成圖檔所在路徑。

  背景代碼此時也非常簡單,隻是簡單地繼承了TextBox控件:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

消息框基礎C#

1 namespace FS.PresentationManagement.Controls
 2 {
 3     /// <summary>
 4     /// 文本消息框控件
 5     /// </summary>
 6     public partial class MessageTextBox : TextBox
 7     {
 8         public MessageTextBox()
 9         {
10             InitializeComponent();        
11         }
12     }
13 }      

  此時的效果如圖所示:

WPF自定義TextBox及ScrollViewer

  看起來還不錯吧,右上角的關閉按鈕由于截圖原因不是很清晰,稍後我們可以看到完整版的要好一些。

  二、改造ScrollViewer控件

  下面介紹本文的核心,如何自定義ScrollViewer控件,當然,我們的目标也不是把它改成什麼奇葩,隻是想把滾動條變得漂亮一點而已。如果使用WPF比較多的朋友會知道,許多控件都是由很多層一層一層地疊加形成可視化樹的,ScrollViewer也不例外,現在通過Template屬性可以完全自己定義其結構。

  要進行改造的ScrollViewer控件就位于第一部分XAML代碼中的省略部分,我現在隻貼出這部分代碼:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

自定義ScrollViewer模版

1 <ScrollViewer x:Name="PART_ContentHost">
  2     <ScrollViewer.Template>
  3         <ControlTemplate TargetType="{x:Type ScrollViewer}">
  4             <Grid Background="{Binding Path=ScrollViewerBackground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}">
  5                 <Grid.ColumnDefinitions>
  6                     <ColumnDefinition />
  7                     <ColumnDefinition Width="Auto"/>
  8                 </Grid.ColumnDefinitions>
  9                 <Grid.RowDefinitions>
 10                     <RowDefinition/>
 11                     <RowDefinition Height="Auto"/>
 12                 </Grid.RowDefinitions>
 13                 <ScrollContentPresenter Margin="5,5,0,5" />
 14                 <ScrollBar Name="PART_VerticalScrollBar" Grid.Column="1" Value="{TemplateBinding VerticalOffset}" Maximum="{TemplateBinding ScrollableHeight}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}">
 15                     <ScrollBar.Template>
 16                         <ControlTemplate TargetType="{x:Type ScrollBar}">
 17                             <!-- 豎向滾動條寬度 -->
 18                             <Grid Width="10">
 19                                 <Grid.RowDefinitions>
 20                                     <RowDefinition Height="1" />
 21                                     <RowDefinition />
 22                                     <RowDefinition Height="1" />
 23                                 </Grid.RowDefinitions>
 24                                 <Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="True">
 25                                     <Track.DecreaseRepeatButton>
 26                                         <!--上空白-->
 27                                         <RepeatButton Command="ScrollBar.PageUpCommand" Opacity="0.5">
 28                                             <RepeatButton.Template>
 29                                                 <ControlTemplate>
 30                                                     <Border Background="{Binding Path=ScrollBarBackground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="5,5,0,0" />
 31                                                 </ControlTemplate>
 32                                             </RepeatButton.Template>
 33                                         </RepeatButton>
 34                                     </Track.DecreaseRepeatButton>
 35                                     <Track.Thumb>
 36                                         <!--滑塊-->
 37                                         <Thumb>
 38                                             <Thumb.Template>
 39                                                 <ControlTemplate>
 40                                                     <Border Background="{Binding Path=ScrollBarForeground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="5" />
 41                                                 </ControlTemplate>
 42                                             </Thumb.Template>
 43                                         </Thumb>
 44                                     </Track.Thumb>
 45                                     <Track.IncreaseRepeatButton>
 46                                         <!--下空白-->
 47                                         <RepeatButton Command="ScrollBar.PageDownCommand" Opacity="0.5">
 48                                             <RepeatButton.Template>
 49                                                 <ControlTemplate>
 50                                                     <Border Background="{Binding Path=ScrollBarBackground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="0,0,5,5" />
 51                                                 </ControlTemplate>
 52                                             </RepeatButton.Template>
 53                                         </RepeatButton>
 54                                     </Track.IncreaseRepeatButton>
 55                                 </Track>
 56                             </Grid>
 57                         </ControlTemplate>
 58                     </ScrollBar.Template>
 59                 </ScrollBar>
 60                 <ScrollBar Name="PART_HorizontalScrollBar" Orientation="Horizontal" Grid.Row="1" Value="{TemplateBinding HorizontalOffset}" Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}">
 61                     <ScrollBar.Template>
 62                         <ControlTemplate TargetType="{x:Type ScrollBar}">
 63                             <!-- 橫向滾動條高度 -->
 64                             <Grid Height="10">
 65                                 <Grid.ColumnDefinitions>
 66                                     <ColumnDefinition Width="1" />
 67                                     <ColumnDefinition />
 68                                     <ColumnDefinition Width="1" />
 69                                 </Grid.ColumnDefinitions>
 70                                 <Track x:Name="PART_Track" Grid.Column="1" IsDirectionReversed="False">
 71                                     <Track.DecreaseRepeatButton>
 72                                         <!--左空白-->
 73                                         <RepeatButton Command="ScrollBar.PageLeftCommand" Opacity="0.5">
 74                                             <RepeatButton.Template>
 75                                                 <ControlTemplate>
 76                                                     <Border Background="{Binding Path=ScrollBarBackground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="5,0,0,5" />
 77                                                 </ControlTemplate>
 78                                             </RepeatButton.Template>
 79                                         </RepeatButton>
 80                                     </Track.DecreaseRepeatButton>
 81                                     <Track.Thumb>
 82                                         <!--滑塊-->
 83                                         <Thumb>
 84                                             <Thumb.Template>
 85                                                 <ControlTemplate>
 86                                                     <Border Background="{Binding Path=ScrollBarForeground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="5" />
 87                                                 </ControlTemplate>
 88                                             </Thumb.Template>
 89                                         </Thumb>
 90                                     </Track.Thumb>
 91                                     <Track.IncreaseRepeatButton>
 92                                         <!--右空白-->
 93                                         <RepeatButton Command="ScrollBar.PageRightCommand" Opacity="0.5">
 94                                             <RepeatButton.Template>
 95                                                 <ControlTemplate>
 96                                                     <Border Background="{Binding Path=ScrollBarBackground,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type TextBox}}}" CornerRadius="0,5,5,0" />
 97                                                 </ControlTemplate>
 98                                             </RepeatButton.Template>
 99                                         </RepeatButton>
100                                     </Track.IncreaseRepeatButton>
101                                 </Track>
102                             </Grid>
103                         </ControlTemplate>
104                     </ScrollBar.Template>
105                 </ScrollBar>
106             </Grid>
107         </ControlTemplate>
108     </ScrollViewer.Template>
109 </ScrollViewer>      

  對應的背景依賴屬性:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

ScrollViewer的背景依賴屬性

1         /// <summary>
 2         /// 卷軸欄背景
 3         /// </summary>
 4         public Brush ScrollViewerBackground
 5         {
 6             get { return (Brush)GetValue(ScrollViewerBackgroundProperty); }
 7             set { SetValue(ScrollViewerBackgroundProperty, value); }
 8         }
 9         public static readonly DependencyProperty ScrollViewerBackgroundProperty =
10             DependencyProperty.Register("ScrollViewerBackground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.LightBlue));
11 
12         /// <summary>
13         /// 滾動條前景
14         /// </summary>
15         public Brush ScrollBarForeground
16         {
17             get { return (Brush)GetValue(ScrollBarForegroundProperty); }
18             set { SetValue(ScrollBarForegroundProperty, value); }
19         }
20         public static readonly DependencyProperty ScrollBarForegroundProperty =
21             DependencyProperty.Register("ScrollBarForeground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.RoyalBlue));
22 
23         /// <summary>
24         /// 滾動條背景
25         /// </summary>
26         public Brush ScrollBarBackground
27         {
28             get { return (Brush)GetValue(ScrollBarBackgroundProperty); }
29             set { SetValue(ScrollBarBackgroundProperty, value); }
30         }
31         public static readonly DependencyProperty ScrollBarBackgroundProperty =
32             DependencyProperty.Register("ScrollBarBackground", typeof(Brush), typeof(MessageTextBox), new PropertyMetadata(Brushes.WhiteSmoke));      

  在構造前台界面時,首先,定義了一個Grid做為容器,并把它分成了四份,分别是内容、豎向滾動條、橫向滾動條、空白。其中,内容位于0行、0列,使用ScrollContentPresenter來表示将要顯示的内容;豎向滾動條位于0行1列,使用ScrollBar來表示;橫向滾動條位于1行0列,使用橫向(Orientation="Horizontal")的ScrollBar來表示。

  然後,分别自定義ScrollBar的樣式。以豎向滾動條為例,自定義ControlTemplate,使用Grid作為容器,把滾動條分為三行,第一行為向上按鈕、第二行為滾動條、第三行為向下按鈕。我這裡出于美觀考慮,把兩個按鈕全省略了(實際上我們很少使用按鈕來上下滾動,大部分時候用的滑鼠中輪和拖動滑塊)。

  滾動條是使用的Track控件,它又包含三個區域,分别是上空白、滑塊、下空白,我們來看個示例圖:

WPF自定義TextBox及ScrollViewer

  Track的DecreaseRepeatButton就是上空白、Thumb則是滑塊、IncreaseRepeatButton是下空白,分别對這三個控件進行樣式自定義即可改變其外觀。需要說明的是豎向滾動條需要把Track的IsDirectionReversed屬性設定為True,橫向則設定為False,不然會出現非常奇怪的現象(原因嘛,大家看屬性名的意思就知道了)。

  最後,還有一點要解釋一下,大家發現許多控件有類似于“PART_***”的名稱,這些名稱請不要随意更改,這是WPF内置的特殊名稱,比如ScrollViewer的“PART_ContentHost”名稱,就是表示這個控件是用于裝載TextBox的文本内容的,并且經過測試,這個名稱隻能用于ScrollViewer或者Adorner、Decorator控件。如果沒有使用這些特殊名稱,可能就無法像你想象中那樣自動完成工作了。

  三、修正一些問題

  為什麼把這做為單獨的一環來讨論呢?因為前面的代碼已經能夠完成基本的工作了,而且出現的問題關系也并不是非常大。但是總會不爽,因為它就不那麼完善,是以,Fix It!

  問題1:滑鼠中輪不能使ScrollViewer上下滾動

   産生這個問題的原因非常詭異,如果不是修改ScrollViewer的Template來完全改變它,而是使用ScrollViewer.Resources來定義ScrollBar的Style則完全不會産生這種問題,但是這無法使的改變各控件的大小和布局。

  另外,如果不是把ScrollViewer的Name設定為“PART_ContentHost”,而是使用<TextBlock Text="{TemplateBinding Text}" TextWrapping="{TemplateBinding TextWrapping}" />放置到ScrollViewer體中,就可以正常滾動。不過這時會導緻無法選中文本了,因為TextBlock中的文本是不支援選中的,特别注意到,這時的滾動效率非常低,滾動時畫面有明顯的遲鈍現象。同樣如果不把ScrollViewer的Name設定為“PART_ContentHost”,而用<Decorator Name="PART_ContentHost" />放置到ScrollViewer體中,雖然選中也能支援,但是依然不能滾動。

  解決方法:

  首先,為ScrollViewer添加Initialized="PART_ContentHost_Initialized"事件,背景增加新的屬性ScrollViewer以便使用:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

初始化滾動條

1     /// <summary>
 2         /// 消息體卷軸欄
 3         /// </summary>
 4         public ScrollViewer ScrollViewer { get; set; }
 5     
 6     // 初始化滾動條
 7         private void PART_ContentHost_Initialized(object sender, EventArgs e)
 8         {
 9             this.ScrollViewer = sender as ScrollViewer;
10         }      

  然後,自己實作中輪滾動方法,為ScrollViewer添加MouseWheel="PART_ContentHost_MouseWheel"事件,添加背景響應代碼:

  private void PART_ContentHost_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)

  {

    ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset - (e.Delta >> 2));

  }

  便可以完美解決滑鼠中輪滾動問題。

  問題2:滑鼠左鍵按住拖動不能使ScrollViewer滾動

   一般來說,我們在任何文字相關軟體上,比如記事本、網頁等,隻要滑鼠左鍵按下拖動選中文本,如果滑鼠超出文本框可顯示範圍,便會自動向滑鼠所在方向滾動文本内容,以實作跨頁選中的效果。但是與問題1一樣,由于更改了ScrollViewer的Template,導緻這個通用功能也需要自己實作了。

  解決方法:

  首先,給前台的最上層元素TextBox添加SelectionChanged="TextBox_SelectionChanged"事件,以追蹤選中時滑鼠所在位置:

1         private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
 2         {
 3             if (ScrollViewer != null && this.SelectedText != "")
 4             {
 5                 var point = System.Windows.Input.Mouse.GetPosition(ScrollViewer);
 6                 // 縱向位移
 7                 double y = point.Y;
 8                 if (y > 0)
 9                 {
10                     y = y - ScrollViewer.ActualHeight;
11                     if (y < 0) y = 0;
12                 }
13                 _ScrollY = y;
14                 // 橫向位移
15                 double x = point.X;
16                 if (x > 0)
17                 {
18                     x = x - ScrollViewer.ActualWidth;
19                     if (x < 0) x = 0;
20                 }
21                 _ScrollX = x;
22             }
23         }      

  說明一下,_ScrollX和_ScrollY是兩個成員屬性,它們分别用來記錄橫向、豎向的滑鼠位移,以用于決定是否滾動。隻有在超出ScrollViewer的範圍時,它們的值才會不為0,當小于0時表示要向上/左滾動,大于0時表示向下/右滾動,它們的絕對值越大,則滾動速度越快。

  現在,滾動量已經能更新了,但滾動觸發條件還需要考慮。首先,橫向和豎向滾動相對于前台界面肯定是異步進行的;其次,已經在滾動時要實時根據滾動量來控制滾動速度;還有,滾動終止條件應該是滾動量為0或者已經滾動到了盡頭。好了,目标明确,需要添加兩個委托來分别處理橫向、豎向滾動,還需要兩個異步操作狀态來表示滾動是否結束,那麼,代碼擴充為:

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

滾動委托

1     // 堅向位移
 2         private double _ScrollY
 3         {
 4             get { return _scrollY; }
 5             set
 6             {
 7                 _scrollY = value;
 8                 // 開啟滾動
 9                 if (_scrollY != 0 && (_ScrollYResult == null || _ScrollYResult.IsCompleted))
10                     _ScrollYResult = _ScrollYAction.BeginInvoke(null, null);
11             }
12         }
13         private double _scrollY;
14 
15         // 橫向位移
16         private double _ScrollX
17         {
18             get { return _scrollX; }
19             set
20             {
21                 _scrollX = value;
22                 // 開啟滾動
23                 if (_scrollX != 0 && (_ScrollXResult == null || _ScrollXResult.IsCompleted))
24                     _ScrollXResult = _ScrollXAction.BeginInvoke(null, null);
25             }
26         }
27         private double _scrollX;
28 
29     // 豎向滾動
30         private Action _ScrollYAction;
31         private IAsyncResult _ScrollYResult;
32     
33         // 橫向滾動
34         private Action _ScrollXAction;
35         private IAsyncResult _ScrollXResult;      

  也就是說,在_ScrollX和_ScrollY更新的時候,程式會進行一次判斷,如果滾動量不為0,而且委托調用沒有開始或者已經結束的時候,就調用委托,開始進行滾動。

  最後,就是編寫滾動委托調用的函數了,分别有兩個函數,在函數内以100ms為一循環,不停地進行滾動,當滾動到結束或者滾動量已經為0時跳出循環,退出函數執行。

WPF自定義TextBox及ScrollViewer
WPF自定義TextBox及ScrollViewer

滾動函數體

1     // 豎向
 2     private void ScrollYMethod()
 3         {
 4             double endOffset = 0;
 5             if (_ScrollY < 0)       // 向上滾動
 6                 endOffset = 0;
 7             else                    // 向下滾動
 8                 ScrollViewer.Dispatcher.Invoke((Action)(() => endOffset = ScrollViewer.ScrollableHeight), null);
 9             // 初始位置
10             double offset = 0;
11             ScrollViewer.Dispatcher.Invoke((Action)(() => offset = ScrollViewer.VerticalOffset), null);
12             // 開始滾動
13             while (offset != endOffset && _ScrollY != 0)
14             {
15                 ScrollViewer.Dispatcher.Invoke((Action)(() =>
16                 {
17                     offset = ScrollViewer.VerticalOffset;
18                     ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + _ScrollY);
19                 }), null);
20                 Thread.Sleep(100);
21             }
22         }
23 
24     // 橫向
25     private void ScrollXMethod()
26         {
27             double endOffset = 0;
28             if (_ScrollX < 0)       // 向左滾動
29                 endOffset = 0;
30             else                    // 向右滾動
31                 ScrollViewer.Dispatcher.Invoke((Action)(() => endOffset = ScrollViewer.ScrollableWidth), null);
32             // 初始位置
33             double offset = 0;
34             ScrollViewer.Dispatcher.Invoke((Action)(() => offset = ScrollViewer.HorizontalOffset), null);
35             // 開始滾動
36             while (offset != endOffset && _ScrollX != 0)
37             {
38                 ScrollViewer.Dispatcher.Invoke((Action)(() =>
39                 {
40                     offset = ScrollViewer.HorizontalOffset;
41                     ScrollViewer.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + _ScrollX);
42                 }), null);
43                 Thread.Sleep(100);
44             }
45         }      

  當然不要忘記,把“_ScrollYAction = ScrollYMethod;”,“_ScrollXAction = ScrollXMethod;”這兩條委托初始化語句放到PART_ContentHost_Initialized事件處理函數中去,不然就白寫了。

  至此,問題2也修改完畢。

  問題3:自動滾動到底部

  實際上這不是問題,而是一個改善,因為一般的滾動條都沒有這個功能。在實用中,假如消息是不停地填寫到消息框中,理想中應該是當拖動滾動條時,不會自動把滾動條更新到最近的一條消息,而是鎖定到拖動的位置(因為我想看的是拖動到的消息)。另外,如果想實時看新消息,就需要自動滾動到最底部。

  當滾動條拖動到最底部時,就開啟自動滾動,每來一條新消息都滾動一次到最底部。如果滾動條不在最底部就不用自動滾動。實作方法就是為TextBox添加TextChanged="TextBox_TextChanged"事件,以判斷是否需要滾動:

1         private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
2         {
3             if (this.Text != "" && ScrollViewer != null)
4             {
5                 // 如果已經拖到最底端,則固定住
6                 if (ScrollViewer.ScrollableHeight == ScrollViewer.VerticalOffset)
7                     ScrollViewer.ScrollToBottom();
8             }
9         }      

  終于碼完字了,多想隻貼代碼啊。放個圖,大家看看吧:

WPF自定義TextBox及ScrollViewer

  請無視上面的那個藍色橫條,那是我另外一個程式中的GridSplitter。這個自定義控件除了支援TextBox的所有屬性外,還可以改變配色(使用公開的屬性),另外還有點選清空、關閉按鈕的操作實作都不難,不貼了,感興趣的下載下傳源代碼看看吧。

  源代碼:

ScrollTest.rar

  轉載請注明原址:

http://www.cnblogs.com/lekko/archive/2013/02/27/2935022.html