Win10 UWP開發系列:開發一個自定義控制元件——帶數字徽章的AppBarButton

yan_xiaodi發表於2017-03-22

最近有個專案有一個這樣的需求,在文章瀏覽頁底部有幾個AppBarButton,其中有一個是評論按鈕,需要在評論按鈕上顯示一個紅色數字,類似微信的新訊息提醒:

image

這種設計在iOS和Android平臺都是很常見的,但UWP上並沒有提供現成的控制元件。所以只能自己實現一個了。

 

做出來效果是這樣的:

 

分析一下實現的思路。首先這還是一個AppBarButton,只是其中增加了一個數字徽章的顯示,這個屬性應該是可以繫結到其他屬性的,如果繫結的值不為0,則顯示數字,如果為0則隱藏數字。因此我們可以通過繼承AppBarButton,並修改其模板來實現這個控制元件。

 

下面動手實踐一下吧。我已經安裝了最新的VS2017,不過使用VS2015的話也是差不多的。首先建立一個名為AppBarBadgeButtonSample的UWP專案:

image

 

image

選擇在Blend中開啟:

image

在頁面上新增一個AppBar:

image

新增後是這樣的結構:

image

image

 

然後需要將預設AppBarButton的模板匯出來,給我們自定義的控制元件用。在按鈕上右鍵選擇編輯模板,編輯副本:

image

 

image

輸入一個名字,為了簡單起見我們就在當前檔案中建立這個樣式好了,點選確定。

切換到XAML模式,可以看到頁面的XAML程式碼中新增了這樣的資源:

image

這個就是AppBarButton的控制元件模板了。我們需要在這個預設模板的基礎上,實現自定義的AppBarBadgeButton。

現在返回到VS2017,在當前專案中新增一個名為Controls的資料夾,再新增一個名為AppBarBadgeButton的資料夾,然後新增一個模板控制元件:

image

確定後,可以看到專案中新增了兩個檔案,一個是cs檔案,一個是一個xaml檔案:

image

這個Generic.xaml即控制元件的模板,但是如果每個控制元件的模板都寫在這裡的話,會顯得比較亂。最好是每個控制元件的cs檔案和xaml檔案都放在一塊。所以在AppBarBadgeButton目錄下新增一個名為AppBarBadgeButton.xaml的樣式檔案,這樣方便管理。同時把控制元件的名稱空間從AppBarBadgeButtonSample.Controls.AppBarBadgeButton改為AppBarBadgeButtonSample.Controls。

image

現在可以把之前生成的預設的AppBarButton的模板複製過來了:

注意,要把key刪掉,TargetType都要改成AppBarBadgeButton:

image

現在需要把這個模板匯入到Generic.xaml中,把Generic.xaml中自動生成的刪掉,改為下面這樣:

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="ms-appx:///Controls/AppBarBadgeButton/AppBarBadgeButton.xaml" />
</ResourceDictionary.MergedDictionaries>

 

這樣所有的控制元件的模板都跟自己的cs檔案放在一塊,Generic.xaml檔案中只放引用位置,專案結構就清晰多了。

 

現在回到AppBarBadgeButton.cs,把控制元件的基類從Control改成AppBarButton。這樣控制元件的所有行為都和預設的AppBarButton一致了。

我們需要一個數字屬性,和一個控制數字是否可見的屬性。新增以下程式碼:

public string Count
{
    get { return (string)GetValue(CountProperty); }
    set { SetValue(CountProperty, value); }
}

// Using a DependencyProperty as the backing store for Count.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty CountProperty =
    DependencyProperty.Register("Count", typeof(string), typeof(AppBarBadgeButton), new PropertyMetadata("0", null));

 

這是一個依賴屬性。注意為什麼數字的型別是string呢?為什麼不設定成int?

賣個關子,先做完再說。

現在可以修改控制元件的模板了。仔細分析模板就能找到需要改的地方,有一個名為ContentRoot的Grid,應該就是這裡了,在名為Content的ContentPresenter後面新增一個Border和一個TextBlock來顯示數字:

<Border CornerRadius="8" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Top" 
                                    Background="Red"  Grid.Row="0" MinWidth="16" Height="16">
                                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
                                           FontSize="10" Text="{TemplateBinding Count}" Foreground="Yellow" />
                            </Border>

 

注意,這裡要使用TemplateBinding將文字繫結到Count屬性。

現在來試試。我們設定Page的x:Name為rootPage,把剛才在主頁面新增的AppBarButton改成自定義的AppBarBadgeButton,不要忘了引入名稱空間:xmlns:localControls="using:AppBarBadgeButtonSample.Controls"

再設定Page的DataContext,並新增一個Count屬性,把AppBarBadgeButton的Count繫結到cs檔案的Count屬性上:

xaml:

<Page
    x:Class="AppBarBadgeButtonSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AppBarBadgeButtonSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:localControls="using:AppBarBadgeButtonSample.Controls"
    mc:Ignorable="d"
    x:Name="rootPage">
    <Page.BottomAppBar>
        <CommandBar>
            <CommandBar.Content>
                <Grid/>
            </CommandBar.Content>
            <localControls:AppBarBadgeButton Icon="Accept" Count="{Binding Count}" Label="AppBarButton"/>
            <AppBarButton Icon="Cancel" Label="AppBarButton"/>
        </CommandBar>
    </Page.BottomAppBar>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    </Grid>
</Page>

 

cs:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        this.Count = 10;
        this.rootPage.DataContext = this;
    }

    public int Count { get; set; }
}

 

我這裡沒有用標準的MVVM,只是簡單演示一下。現在執行下看看:

image

效果出來了。

現在思考剛才提到的問題,為什麼在DataContext裡的屬性可以設定為int,而控制元件本身的依賴屬性必須設定為string呢?如果有興趣可以將依賴屬性改為int試試,結果就是按鈕上什麼也顯示不出來:

image

 

這是因為,在使用TemplateBinding的時候,必須保證source屬性和target屬性的值的型別是匹配的,TextBlock的Text屬性是string,那麼用於繫結的屬性的型別也必須是string。在使用TemplateBinding時,沒有機會進行自動的型別轉換。如果型別不匹配,則無法繫結。可參考:https://docs.microsoft.com/en-us/windows/uwp/xaml-platform/templatebinding-markup-extension

現在可以顯示數字了,但是還需要做的更完善一點。有數字的時候顯示,如果為0的時候就隱藏掉。所以再做一點小小的工作:

新增一個控制是否顯示數字的屬性:

public Visibility BadgeVisibility
{
    get { return (Visibility)GetValue(BadgeVisibilityProperty); }
    set { SetValue(BadgeVisibilityProperty, value); }
}

// Using a DependencyProperty as the backing store for BadgeVisibility.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty BadgeVisibilityProperty =
    DependencyProperty.Register("BadgeVisibility", typeof(Visibility), typeof(AppBarBadgeButton), new PropertyMetadata(Visibility.Collapsed, null));

 

 

當Count值變化時,將其設定為隱藏:

public string Count
        {
            get { return (string)GetValue(CountProperty); }
            set { SetValue(CountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Count.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CountProperty =
            DependencyProperty.Register("Count", typeof(string), typeof(AppBarBadgeButton), new PropertyMetadata("0", OnCountChanged));

        private static void OnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            int count = 0;
            int.TryParse(e.NewValue.ToString(), out count);
            if (count != 0)
            {
                ((AppBarBadgeButton)d).SetValue(BadgeVisibilityProperty, Visibility.Visible);
            }
            else
            {
                ((AppBarBadgeButton)d).SetValue(BadgeVisibilityProperty, Visibility.Collapsed);
            }
        }

 

 

最後將數字的Visibility屬性繫結到這個值上:

<Border CornerRadius="8" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Top" 
                                    Background="Red" Visibility="{TemplateBinding BadgeVisibility}" Grid.Row="0" MinWidth="16" Height="16">

 

 

程式碼已開源:https://github.com/yanxiaodi/UWP-AppBarBadgeButton

相關文章