WPF中下拉框即可以選擇項也可以作為唯讀文字方塊使用

2023-01-31 09:00:27

1、需求

當前在開發的系統需要一個這樣的控制元件。

(1)可以選擇已有的選擇項,類似於ComboBox選擇;

(2)可以通過其他按鈕點選,選擇一個檔案,選擇後,把檔案路徑顯示到控制元件上,並且處於唯讀狀態,行為和唯讀狀態下的TextBox保持一致。

更直觀些,就是實現類似ArcMap中Toolbox中的資料集選擇下拉框,如下圖所示。

截圖.png

該控制元件可以通過下拉的方式,選擇左側的圖層資料,又可以通過點選右側的開啟檔案按鈕,彈出開啟檔案對話方塊,選擇要設定的資料檔案。該控制元件還可以通過鍵盤輸入,但我們的需求是禁止輸入,只能選擇和通過開啟檔案對話方塊設定。

2、現有的ComboBox

我計劃在WPF中的ComboBox控制元件的基礎上進行開發,首先看通過簡單的屬性設定是否能夠滿足要求。

(1)不設定任何引數

在不設定任何引數的情況下,效果如下圖所示。

截圖.png

可以顯示底圖上載入的資料,但點選【Select】按鈕,通過設定ComboBox.Text屬性,資料顯示不出來,這種效果離我們想要的還差十萬八千里。

(2)設定ComboBox.IsEditable=true

下面我們嘗試通過設定ComboBox的實行看能不能達到我們想要的效果。首先把ComboBox.IsEditable屬性設定為true,然後測試下Text屬性設定後是否有效果了。

截圖.png

目前可以把選擇的檔案路徑設定到ComboBox.Text了,並且可以正常顯示,但滑鼠點上去,文字方塊的內容可以修改,這不是我們想要的。於是發現ComboBox有個IsReadOnly屬性,把該屬性設定為true嘗試一下試試。

截圖.png

目前下拉框中的內容不能修改了,但實際操作的時候會發現有些彆扭。當滑鼠點選下拉框,啟用該控制元件的時候,裡面的文字預設處於全選狀態,此時想拖動滑鼠,把文字拖動到尾部是操作不了的,只能再次點選下拉框中的內容,才可以和普通文字方塊的操作一樣通過滑鼠拖動檢視。

經過各種嘗試,發現當ComboBox控制元件GotFocus的時候,裡面的文字會預設處於全選狀態,這個我們需要解決一下。

3、使用VisualTreeHelper單獨處理TextBox

在網上查詢,發現ComboBox在IsEditable=true的狀態下,是有很多個控制元件組合而成的。如下圖所示。

截圖.png

我們可以呼叫VisualTreeHelper,獲取這個TextBox,不過需要在Load後,再呼叫,不然這些控制元件是獲取不到的。

this.Loaded += (x, y) =>
{
    var myMainGrid = VisualTreeHelper.GetChild(this, 0);
    var myTextBox = VisualTreeHelper.GetChild(myMainGrid, 4) as TextBox;
    myTextBox.IsReadOnly = true;
    myTextBox.IsReadOnlyCaretVisible = true;
};

獲取TextBox後,我們需要解決ComboBox啟用後,文字全選的問題。程式碼如下。

myTextBox.GotFocus += (m, n) =>
{
    myTextBox.SelectionOpacity = 0;
    this._IsNeedClearSelection = true;
};

該程式碼的的作用是當TextBox得到焦點後,立刻把TextBox中文字選中的背景顏色的透明度設定為0,這樣使用著就感覺不出來文字被選中了。

滑鼠左鍵彈起前,需要清空選中文字,並把遊標放到滑鼠點選處,並還原文字選中的顏色,程式碼如下。

myTextBox.PreviewMouseLeftButtonUp += (m, n) =>
{
    if (this._IsNeedClearSelection == false)
    {
        return;
    }
    var myPosition = n.GetPosition(myTextBox);
    int mySelectionStart = myTextBox.GetCharacterIndexFromPoint(myPosition, true);
    myTextBox.Select(mySelectionStart, 0);
    this._IsNeedClearSelection = false;
    myTextBox.SelectionOpacity = 0.4;
};

此時,基本上ComboBox能夠滿足我們的需求了,效果如下圖所示。

截圖.png

但還有一個問題,就是在ComboBox在IsEditable=true的狀態下,滑鼠移動到可選項上的時候,選擇項不高亮了。為了解決這個問題,嘗試了很多方法,都不行,準備放棄,就這樣了。

因為系統使用了DEV for WPF UI庫,忽然想到了WPF DEV中的ComboBoxEdit,之前測試過,通過設定屬性,滿足不了需求。但沒有使用VisualTreeHelper,深入的去測試,那就再嘗試下試試。

4、使用WPF DEV中的ComboBoxEdit

測試的時候,ComboBoxEdit在任何屬性都不設定的情況下,除了文字可以編輯,其他的都可以滿足要求。於是我們設定IsReadOnly=True,但這個時候,下拉框中的可選擇項都處於不可用狀態,也不能選擇,所以IsReadOnly屬性不能設定成True。嘗試了一下有可能性的其他屬性,例如EditMode等,都不能滿足需求。

但在測試ComboBox的時候,知道了WPF的視覺化樹這個概念,可以通過VisualTreeHelper獲取組成控制元件的子控制元件。於是載入後,在Load事件中,我們檢視下ComboBoxEdit組織樹,如下圖所示。

截圖.png

我們看到了裡面有個TextBox控制元件,這個就是顯示文字的控制元件了,感覺是不是獲取到這個TextBox然後把該TextBox設定成唯讀是不是問題就完美解決了?

這個樹比較深,我們就找了一段根據型別獲取元素的程式碼,如下圖所示。

public static List<T> FindVisualChild<T>(DependencyObject pDependencyObject) where T : DependencyObject
{
    List<T> myTList = new List<T> { };
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(pDependencyObject); i++)
    {
        DependencyObject myChild = VisualTreeHelper.GetChild(pDependencyObject, i);
        if (myChild != null && myChild is T myT)
        {
            myTList.Add(myT);
            List<T> myChildOfChildren = FindVisualChild<T>(myChild);
            if (myChildOfChildren != null)
            {
                myTList.AddRange(myChildOfChildren);
            }
        }
        else
        {
            List<T> myChildOfChildren = FindVisualChild<T>(myChild);
            if (myChildOfChildren != null)
            {
                myTList.AddRange(myChildOfChildren);
            }
        }
    }
    return myTList;
}

獲取TextBox和設定其屬性的程式碼如下。

public class DatasetComboBoxExUI : ComboBoxEdit
{
    public DatasetComboBoxExUI()
    {
        this.Loaded += (x, y) =>
        {
            List<TextBox> myTextBoxList = VisualTreeExHelper.FindVisualChild<TextBox>(this);
            if (myTextBoxList.Count == 0)
            {
                return;
            }
            var myTextBox = myTextBoxList[0];
            myTextBox.IsReadOnly = true;
            myTextBox.IsReadOnlyCaretVisible = true;
        };
    }
}

啟動測試,效果非常完美,正是我們需要的,介面如下圖所示。

截圖.png

5、總結

最開始測試Dev中的ComboBoxEdit,通過設定屬性各種嘗試,發現滿足不了需求。既然ComboBoxEdit滿足不了需求,那就在WPF原生態的ComboBox上測試吧。

開始也是設定ComboBox的屬性,各種測試後,發現還是不行。在網上搜尋的時候,發現可以通過VisualTreeHelper獲取組成ComboBox的UI樹,從而對其內部的控制元件進行操作。VisualTreeHelper之前也用過,但在這塊確實沒往這塊想。

通過VisualTreeHelper獲取ComboBox中的TextBox,通過各種嘗試,終於達到了一個比較滿意的效果,此時已經花了一下午的時間。晚上下班回家後,想把這個過程整理一下,使用的時候,ComboBox在IsEditable=true的狀態下,滑鼠移動到可選項上的時候,選擇項不高亮了。於是想把這個問題再解決下,以達到最完美效果。

但花了兩個小時左右,一點思路也沒有,在網上也沒找到相關的資料。此時忽然想到,此前測試Dev中的ComboBoxEdit時候,還沒想到過VisualTreeHelper這個方法,所以還沒測試,那就測試下試試。

於是修改了程式碼,檢視了下ComboBoxEdit的視覺化樹,發現裡面有個TextBox,此時感覺希望非常大,因為當時放棄ComboBoxEdit是因為顯示的內容可編輯,又不能設定ComboBoxEdit的IsReadOnly屬性為True。現在看到ComboBoxEdit裡面有個TextBox,如果我們獲取TextBox,把這個TextBox的IsReadOnly屬性為True不就可以了?

於是按照這個思路嘗試了一下,最後效果非常完美,完全滿足系統需求。