.net Framework 源代碼 · ScrollViewer

来源:https://www.cnblogs.com/lindexi/archive/2018/04/29/8970485.html
-Advertisement-
Play Games

本文是分析 .net Framework 源代碼的系列,主要告訴大家微軟做 ScrollViewer 的思路,分析很簡單 看完本文,可以學會如何寫一個 ScrollViewer ,如何定義一個 IScrollInfo 或者給他滾動添加動畫 ...


本文是分析 .net Framework 源代碼的系列,主要告訴大家微軟做 ScrollViewer 的思路,分析很簡單

看完本文,可以學會如何寫一個 ScrollViewer ,如何定義一個 IScrollInfo 或者給他滾動添加動畫

使用

下麵告訴大家如何簡單使用 ScrollViewer ,一般在需要滾動的控制項外面放一個 ScrollViewer 就可以實現滾動。

  <ScrollViewer HorizontalScrollBarVisibility="Auto">
    <StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
      <TextBlock TextWrapping="Wrap" Margin="0,0,0,20">Scrolling is enabled when it is necessary. 
      Resize the window, making it larger and smaller.</TextBlock>
      <Rectangle Fill="Red" Width="500" Height="500"></Rectangle>
    </StackPanel>
  </ScrollViewer>

但不是所有的控制項外面放一個 ScrollViewer 都能實現滾動,因為滾動實際上需要控制項自己做。

原理

下麵來告訴大家滾動是如何做的。

一個最簡單的方法是設置元素的 transForm.Y 通過這個方式進行滾動是最簡單的方法,但是缺點是其他控制項不能做其他的移動。

在 ScrollViewer 存在兩個滾動方式,物理滾動 和 邏輯滾動,如果使用 物理滾動 那麼滾動就是ScrollViewer做的,如何使用邏輯滾動,那麼滾動就是控制項自己做的。

那麼我從 ScrollViewer 接收輸入開始講起

輸入

如果大家使用 ScrollViewer 進行滾動,那麼也許會遇到一個神奇的需求,如何在觸摸下滾動。是的,如果使用一個簡單的 ScrollViewer 是無法使用觸摸滾動

請看代碼,寫一個簡單的 ScrollViewer 裡面有一些矩形,可以看到這時可以進行滑鼠滾動,但是觸摸是無法滾動。

    <Grid>
        <ScrollViewer>
            <StackPanel x:Name="HcrkKmqnnfzo"></StackPanel>
        </ScrollViewer>
    </Grid>

在後臺遍歷顏色然後添加

        public MainWindow()
        {
            InitializeComponent();

            foreach (var temp in typeof(Brushes)
                .GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
                .Select(temp => temp.GetValue(null, null)))
            {
                var rectangle = new Rectangle
                {
                    Height = 20,
                    Fill = (Brush)temp
                };

                HcrkKmqnnfzo.Children.Add(rectangle);
            }
        }

代碼:WPF ScrollView 代碼解釋 1.1-CSDN下載

如果沒有csdn積分,嘗試使用 我的網盤,但是我的網盤如果過期請告訴我

如果需要在觸摸使用滾動,那麼需要設置PanningMode,可以設置支持垂直拖動。

如果這時設置了PanningMode,就會發現拖動時讓視窗抖動,這時需要在視窗重寫 OnManipulationBoundaryFeedback ,請看下麵代碼。函數裡面什麼都不要寫,詳細請看 https://stackoverflow.com/a/6918131/6116637

       protected override void OnManipulationBoundaryFeedback(ManipulationBoundaryFeedbackEventArgs e)
        {
        }

修改後的代碼:WPF ScrollView 代碼解釋 1.2-CSDN下載

那麼在滑鼠滾動是如何收到滾動?

從微軟源代碼可以看到 ScrollViewer 繼承 ContentControl,所以可以重寫 OnMouseWheel ,請看他的代碼

      protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            if (e.Handled) { return; }
 
            if (!HandlesMouseWheelScrolling)
            {
                return;
            }
 
            if (ScrollInfo != null)
            {
                if (e.Delta < 0) { ScrollInfo.MouseWheelDown(); }
                else { ScrollInfo.MouseWheelUp(); }
            }
 
            e.Handled = true;
        }

實際上 ScrollViewer 是不做滾動的,實際的滾動是 ScrollInfo 進行滾動。

ScrollInfo

那麼 ScrollInfo 是什麼,實際上他是一個介面,在 ScrollViewer 裡面放的控制項實際上不是直接放在 ScrollViewer 里,控制項是放在 ScrollContentPresenter,而 ScrollContentPresenter 是寫在 ScrollViewer 的 Style 里,在 ScrollViewer 可以看到這個代碼

[TemplatePart(Name = "PART_ScrollContentPresenter", Type = typeof(ScrollContentPresenter))]

但是從垃圾微軟的代碼可以看到,沒有屬性直接使用這個,而是在使用的地方這樣寫GetTemplateChild(ScrollContentPresenterTemplateName) as ScrollContentPresenter;

這樣寫的性能是比較差的。

那麼他是如何給 ScrollInfo 賦值?實際上在這個類的 HookupScrollingComponents 就是給 ScrollInfo 賦值,在 HookupScrollingComponents 調用的地方就是 OnApplyTemplate 所以大家可以看到,在初始化的時候就已經知道了控制項。

從垃圾微軟的源代碼可以看到 HookupScrollingComponents 的邏輯,首先是判斷屬性CanContentScroll 判斷元素里的控制項是否可以滾動,如果元素里的控制項可以滾動,那麼再判斷元素里的控制項是不是繼承IScrollInfo如果是的話,嗯,沒了,就把 ScrollInfo 賦值。如果裡面的控制項不是繼承IScrollInfo,那麼判斷一下他是不是處於列表,如果是的話就拿列表ItemsPresenter作為ScrollInfo。如果還是拿不到,只好用自己作為ScrollInfo

從這裡可以看到 CanContentScroll 如果沒有設置,就直接使用這個類,也就是物理滾動就是這個類做的。如果一個元素不在列表內,不繼承 IScrollInfo 那麼即使設置使用邏輯滾動,實際上也是物理滾動。物理滾動就是元素不知道滾動,所有的移動都是元素無法控制。和物理滾動不同,邏輯的就是元素控制所有滾動。

物理滾動

下麵來告訴大家,物理滾動是如何做,實際上的滾動就是在佈局中使用下麵的代碼,讓元素佈局在滾動的地方,所以看起來就是元素滾動

                  Rect childRect = new Rect(child.DesiredSize);
 
                        if (IsScrollClient)
                        {
                            childRect.X = -HorizontalOffset;
                            childRect.Y = -VerticalOffset;
                        }
 
                        //this is needed to stretch the child to arrange space,
                        childRect.Width = Math.Max(childRect.Width, arrangeSize.Width);
                        childRect.Height = Math.Max(childRect.Height, arrangeSize.Height);
 
                        child.Arrange(childRect);

可以看到佈局設置反過來的 HorizontalOffset 作為元素的 x 移動,通過這樣就可以讓元素移動

但是元素如果移動在 ScrollViewer 外面,如何裁剪?實際上就是使用重寫了 GetLayoutClip 進行裁剪

 return new RectangleGeometry(new Rect(RenderSize));

從代碼可以知道,實際上的 ScrollViewer 是不會滾動元素的,滾動元素的是 ScrollViewer 裡面的元素,滾動的方式一般都使用在佈局的時候設置元素的 X、Y 來讓元素滾動。我看了 StackPanel 和其他幾個類,都是使用這個方式,因為對比 Translate 的方式,這個方法不會用到 Translate 也就不會在用戶修改 Translate 的時候無法移動。另外這個方法是在佈局做的,直接計算,如果修改 Translate 還需要在佈局重新計算,所以這個方法的性能會比較高。

觸摸輸入

那麼 ScrollViewer 是如何在觸摸的時候獲得輸入?實際上在觸摸的時候用的是 Manipulation ,在判斷 PanningMode 給值

                    if (panningMode == PanningMode.HorizontalOnly)
                    {
                        e.Mode = ManipulationModes.TranslateX;
                    }
                    else if (panningMode == PanningMode.VerticalOnly)
                    {
                        e.Mode = ManipulationModes.TranslateY;
                    }
                    else
                    {
                        e.Mode = ManipulationModes.Translate;
                    }

所以在 ManipulationDelta 可以拿到移動的值,因為直接拿到的值就是用戶希望的路徑所以直接設置不需要計算

但是需要倍數 PanningRatio ,如果需要慣性,那麼只需要設置慣性就可以。

大概整個源代碼只有這些,很多的代碼都是在判斷邊界,還有處理一些用戶輸入。

在觸摸的時候,核心的代碼是 ManipulateScroll ,傳入了當前的移動和累計的移動、是否水平移動。通過判斷當前的移動是否有移動然後乘以倍數,然後通過設置 HorizontalOffset 這幾個屬性的值,重新佈局就可以。

所以所有的代碼實際上就是獲得輸入,然後傳入給對應的 ScrollInfo ,通過 ScrollInfo 實現的方法做具體的業務。

不過 ScrollViewer 不是直接傳入 ScrollInfo 需要移動的,而且發送命令

     
        public void ScrollToHorizontalOffset(double offset)
        {
            double validatedOffset = ScrollContentPresenter.ValidateInputOffset(offset, "offset");
 
            // Queue up the scroll command, which tells the content to scroll.
            // Will lead to an update of all offsets (both live and deferred).
            EnqueueCommand(Commands.SetHorizontalOffset, validatedOffset, null);
        }
 

然後在具體的函數 ExecuteNextCommand 拿出一個個的命令,進行移動

     private bool ExecuteNextCommand()
        {
            IScrollInfo isi = ScrollInfo;
 
            Command cmd = _queue.Fetch();
            switch(cmd.Code)
            {
                case Commands.LineUp:    isi.LineUp();    break;
                case Commands.LineDown:  isi.LineDown();  break;
                case Commands.LineLeft:  isi.LineLeft();  break;
                case Commands.LineRight: isi.LineRight(); break;
                //去掉差不多的代碼
                case Commands.Invalid: return false;
            }
            return true;
        }

在輸入的時候可能輸入太快,而佈局不是立刻進行佈局,從代碼可以看到,移動的業務就是在佈局修改值,但是佈局修改不是優先順序很高的,但是輸入的優先順序是很高的,可能在佈局的過程就不停輸入。所以就需要把輸入的命令放入,使用一個函數一個個拿出來,對不同的命令處理,最後再佈局。

參見:

在WPF中實現平滑滾動 - 天方 - 博客園

IScrollInfo in Avalon part I – BenCon's WebLog

IScrollInfo in Avalon part II – BenCon's WebLog

IScrollInfo in Avalon part III – BenCon's WebLog

IScrollInfo tutorial part IV – BenCon's WebLog

其他源代碼分析

.net Framework 源代碼 · ScrollViewer

.net源碼分析 – List

一站式WPF--依賴屬性(DependencyProperty)一 - 周永恆 - 博客園

我搭建了自己的博客 https://lindexi.gitee.io/ 歡迎大家訪問,裡面有很多新的博客。只有在我看到博客寫成熟之後才會放在csdn或博客園,但是一旦發佈了就不再更新

如果在博客看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎大家加入

知識共用許可協議
本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名林德熙(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child. Input Each input file contain ...
  • 二、springboot+mybatis的使用 1.springboot的註解:@SpringBootApplication :啟動項目:整合常用註解(@Configuration,@EnableAutoConfiguration,@ComponentScan)/掃包作用(只能在當前同級包下) @E ...
  • 作為一個Python Web 開發工程師,pyg0每天都喜滋滋的寫著基於各種web框架的業務代碼。 突然有一天,技術老大過來跟pyg0說,嘿,我們要新上線一個服務,你來幫我部署一下吧。不用太複雜。用gunicorn跑flask, 啟8個進程, 用gevent模式跑就可以。這個很好配,給你一個小時吧。 ...
  • Java的5個標簽庫:核心(c)、格式化(fmt)、函數(fn)、SQL(sql)、XML(x) SQL、XML庫不推薦使用 核心標簽庫(c) <c:out> <c:if> <c:choose>、<c:when>、<c:otherwise> <c:forEach>、<c:forTokens> <c: ...
  • 一、字典的定義方法: 1、dic = {'name':'Karen','age':22,'hobby':'girl','is_handsome':True} print(dic) #==>{'name':'Karen'} dic = {'name':'Karen','age':22,'hobby': ...
  • 1.java程式的執行過程 java源文件->解析器->class文件->java類載入器->java運行時數據區->執行引擎 2.我們接下來看一下java運行時數據區 包含程式計數器,虛擬機棧,本地方法棧,方法區,堆,其中程式計數器,虛擬機棧,本地方法區屬於指令,方法區和堆屬於數據。 一、程式計數 ...
  • 一般工程人員都知道 TDD 的使用方式,也明白 UnitTest 對於程式碼品質有著良好的保護 但比較少去探討的另一個好處應該是「學習」這件事了 雖然一般來說我們寫 Testing 其實是針對已知的事物(Business logic)去做測試與保護 比較少做未知的探索測試,但在一般有寫 Testin ...
  • .NET Core 基於Nuget包。它是一個.nupkg尾碼的zip文件。工具dotnet 工具vs2017 的程式包管理控台這兩個工具都可以用命令行來下載安裝,更新,上傳包(上傳要先在網站註冊賬號);也可以用vs裡面的UI工具管理。項目文件控制包項目文件(.csproj尾碼)是一個xml文件,描... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...