WPF 簡易手風琴 (ListBox+Expander)

来源:http://www.cnblogs.com/smile870912/archive/2017/05/12/6819550.html
-Advertisement-
Play Games

概述 之前聽說很多大神的成長之路,幾乎都有個習慣——寫博文,可以有效的對項目進行總結、從而提高開發的經驗。所以初學WPF的我想試試,順便提高一下小學作文的能力。O(∩_∩)O哈哈~ 讀萬卷書不如行萬里路,實踐是最好的導師!最近在學習WPF,也嘗試著做了一些小Demo,但並沒有真正的使用WPF的開發模 ...


概述

    之前聽說很多大神的成長之路,幾乎都有個習慣——寫博文,可以有效的對項目進行總結、從而提高開發的經驗。所以初學WPF的我想試試,順便提高一下小學作文的能力。O(∩_∩)O哈哈~

讀萬卷書不如行萬里路,實踐是最好的導師!最近在學習WPF,也嘗試著做了一些小Demo,但並沒有真正的使用WPF的開發模式——數據推動UI,最近偶然的機會也是工作需求,就嘗試著寫了一個簡易的手風琴控制項,

因為初學的原因,可能在邏輯上,代碼上有些欠缺,還請大神們多多指點,在這裡先感謝各位!(下麵是效果圖)

 

 

思路 

     剖析效果,拆分控制項,我當時的想法是Grid容器+基本控制項進行手繪、最後在添加一些展開收起的動畫,想法很美好,現實很殘酷!後來詢問了一下大神,給出的建議是ListBox+Expander實現,就這樣在大神們的指點下開始了。

數據結構

    ExpanderClass類:標題圖片、標題名稱、按鈕圖片集合 ; ImgUrlClass類:按鈕圖片。

    public class DataSourceClass
    {
        /// <summary>
        /// 數據源
        /// </summary>
        public static List<ExpanderClass> GetDateSource()
        {
            List<ExpanderClass> exLst = new List<ExpanderClass>();
            List<ImgUrlClass> lst = new List<ImgUrlClass>();
            for (int i = 1; i < 10; i++)
            {
                lst.Add(new ImgUrlClass() { ImageUrl = string.Format("Images/h{0}.png", i) });
            }
            exLst.Add(new ExpanderClass()
            {
                Title = "我是第一行哦!",
                ImgUrl = "Images/Left.png",
                ImgLst = lst
            });

            lst = new List<ImgUrlClass>();
            for (int i = 1; i < 10; i++)
            {
                lst.Add(new ImgUrlClass() { ImageUrl = string.Format("Images/h1{0}.png", i) });
            }
            exLst.Add(new ExpanderClass()
            {
                Title = "我是第二行哦!!",
                ImgUrl = "Images/Right.png",
                ImgLst = lst
            });

            lst = new List<ImgUrlClass>();
            for (int i = 1; i < 10; i++)
            {
                lst.Add(new ImgUrlClass() { ImageUrl = "Images/h10.png" });
            }
            exLst.Add(new ExpanderClass()
            {
                Title = "我是第三行哦!!!",
                ImgUrl = "Images/Up.png",
                ImgLst = lst
            });
            return exLst;
        }
    }

    public class ExpanderClass
    {
        /// <summary>
        /// 標題
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 標題圖片
        /// </summary>
        public string ImgUrl { get; set; }

        /// <summary>
        /// 按鈕圖片集合
        /// </summary>
        public List<ImgUrlClass> ImgLst { get; set; }

        public ExpanderClass()
        {
            Title = string.Empty;
            ImgUrl = string.Empty;
        }
    }

    public class ImgUrlClass
    {
        public ImgUrlClass()
        {
            ImageUrl = string.Empty; 
        }

        /// <summary>
        /// 按鈕圖片
        /// </summary>
        public string ImageUrl { get; set; } 
    }

 

Expander

     首先添加一個WPF用戶控制項AccordionControl 然後添加一個Expander控制項,然後在裡面添加個Image後運行看一下效果

  <Expander x:Name="expander" Header="Expander"  >
            <Grid Background="#FFE5E5E5">
                <Image Source="Image/Button/h1.png" />
            </Grid>
        </Expander>

這樣一個展開收起的Expander 就OK了,接下里根據設計需求分析,我們需要修改Expander的 Header、Content 兩部分。

Header(頭部):修改背景色、添加標題圖片、標題。

Content(內容):多個圖片按鈕組成(這裡需要思考一下,結果集是多張圖片,所以需要迴圈載入圖片,所以這裡使用了ListBox控制項)。

     <Expander>
            <Expander.Header>
                <StackPanel Orientation="Horizontal" Background="#3399ff"  >
                    <Image Source="Image/Button/h1.png" Height="16"/>
                    <TextBlock  Text="我是標題哦" VerticalAlignment="Center"/>
                </StackPanel>
            </Expander.Header>
            <Expander.Content  >
                <ListBox ItemsSource={binding}>                
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Image Source="Image/Button/h1.png"   />
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </Expander.Content>       
        </Expander>

綁定數據源運行後

距離我們的設計是不是進了一步,我們需要思考一下 設計圖給出的內容中按鈕是 橫向 展示,並且根據寬度自動換行? WPF中我知道的可以實現此功能的控制項 WrapPanel、TextBlock,WrapPanel更好用,我們來修改下ListBox中ItemsPanel模板,並且將ListBox水平滾動條取消

   <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
                    <ListBox.ItemsPanel>
                        <ItemsPanelTemplate>
                            <WrapPanel  Orientation="Horizontal"  Background="Transparent" />
                        </ItemsPanelTemplate>
                    </ListBox.ItemsPanel>
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Image Source="{Binding ImageUrl}"   />
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>

運行,效果不錯吧。

  

這樣一個簡單的Expander完成了,實際工作中我們可能需要多個這樣的Expander組合使用,很簡單,ListBox嵌套Expander 進去就可以了。

完善功能

運行Demo,可能會發現一個問題,點擊第一行的Expander時候 其它的並沒有收起,怎麼實現點擊其中一個讓其他自動收起呢? 

方案一:RadioButton;  單選的效果是RadioButton被分配到相同的組中 GroupName ,結合Demo 我們可以將Expander封裝到RadionButton中,然後給GroupName賦值 這樣就可以實現效果。

方案二:ListBox的ListBoxItem.IsSelected 是否選中; Expander中IsExpanded屬性的意思是內容視窗是否可見,當我們選擇一個ListBoxItem時將IsDelected綁定到IsExpanded中就可以實現。

最初,我是使用RadionButton實現的,後大神提出建議為何不試試ListBoxItem呢?所以我修改了源碼。 

<ListBox x:Name="ItemBox"   ItemsSource="{Binding}"  Width="400" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Expander   Width="{Binding Path=Width, ElementName=ItemBox}"  Tag="{Binding}"     IsExpanded="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=ListBoxItem},  Path=IsSelected}"  >
                        <Expander.Header>
                            <StackPanel Orientation="Horizontal" Background="#3399ff"  Width="{Binding Path=Width, ElementName=ItemBox}"  >
                                <Image Source="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.ImgUrl}" Height="16" Width="16" VerticalAlignment="Center"/>
                                <TextBlock   VerticalAlignment="Center"   Text="{Binding    RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.Title}"  />
                            </StackPanel>
                        </Expander.Header>
                        <Expander.Content  >
                            <ListBox ItemsSource="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.ImgLst}"   >
                                <ListBox.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <WrapPanel  Orientation="Horizontal"   HorizontalAlignment="Center" />
                                    </ItemsPanelTemplate>
                                </ListBox.ItemsPanel>
                                <ListBox.ItemTemplate>
                                    <DataTemplate>
                                        <Image Source="{Binding ImageUrl}" />
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                        </Expander.Content>
                    </Expander>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

 解析下數據綁定:

WPF基礎Binding(綁定)這裡用到兩種方式(概念上的可以參考百度這裡不再熬述):

1、ElementName指定Source:在C#代碼中可以直接把對象作為Source賦值給Binding,但是XAML無法訪問對象,只能使用Name屬性來找到對象。

 Width="{Binding Path=Width, ElementName=ItemBox}" 

2、RelataveSource:通過Binding的RelataveSource屬性相對的指定Source:當控制項需要關註自己的、自己容器的或者自己內部某個屬性值就需要使用這種辦法。 

 IsExpanded="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=ListBoxItem},  Path=IsSelected}"

* 這裡有個註意點Expander.Header中並不能直接得到數據源 我這裡是將數據源綁定到Expander.Tag中 然後通過 RelataveSource 向上查找的方式獲取數據源

<TextBlock   VerticalAlignment="Center"   Text="{Binding    RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.Title}"  />

這樣整體的數據綁定就完成了。 

現在思考一下這樣個需求:當我們點擊按鈕後更改背景圖片,點擊其他按鈕後,點擊過的按鈕還原?

分析:根據需求分解兩個動作效果.

1、按鈕之間的切換並且還原樣式:這裡的效果類似於之前的Expander 之間的展開與閉合,所以這裡依然使用 RadioButton 來實現,RadionButton改變樣式成為圖片按鈕 。

2、點擊按鈕更改背景圖片:根據RadionButton的IsChecked屬性來實現,將Image 的Source 與IsChecked 關聯起來,通過轉換器根據相應的值返回不同的圖片。 

<RadioButton x:Name="rdoImg" GroupName="rdoImgGroup"  Cursor="Hand" Width="80" Height="80" >
    <RadioButton.Template>
        <ControlTemplate>
            <Image>
                <Image.Source>
                    <MultiBinding Converter="{StaticResource IsDataConverter}"  >
                        <Binding  Path="IsChecked"  ElementName="rdoImg"  />
                        <Binding  Path="ImageUrl" />
                    </MultiBinding>
                </Image.Source>
            </Image>
        </ControlTemplate>
    </RadioButton.Template>
</RadioButton>

 MultiBinding:多番綁定

在開發的過程中遇到了一個很有意思的事情,我為了方便直接將整個實體綁定進去,然後在轉換器中進行判斷可以得到圖片進行處理,運行後但圖片沒變化。難道轉換器無效,IsChecked沒變?

帶著疑問我修改了一下,只綁定IsChecked 看看到底是否發生變化,運行,結果發生變化,圖片也變了(這裡修改了轉換器 返回一個固定圖片)。

我個人的理解 Binding 應該只針對一個屬性才有效,可是我想把圖片路徑也傳過去,這樣更好的處理,那麼MultiBinding就用到了 

這裡需要註意一點:MultiBinding 轉換器 繼承 IMultiValueConverter 介面,Binding 繼承 IValueConverter 介面。

MultiBinding : IMultiValueConverter 
Binding       :  IValueConverter

 這樣需求就實現了,運行程式

  

當我們切換Expander時,發現個問題按鈕並沒有還原?

思路:在點擊Expander時,將RadioButton的IsChecked改為False就可以了,這是因為我們已經將Image的Source與RadioButton的IsChecked關聯。

所以只要將RadioButton的IsChecked 與 Expander的IsExpanded 關聯就可以。 

 <RadioButton x:Name="rdoImg" GroupName="rdoImgGroup"  Cursor="Hand" Width="80" Height="80" IsChecked="{Binding  Path=IsExpanded,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Expander},Converter={StaticResoce IsCheckedConvert},Mode=OneWay}"    >

 這裡需要註意一點 IsChecked 與 IsExpanded 的綁定關係是單向的 Mode=OneWay,這樣保證每次點擊Expander 返回的都是False,不然會報錯,因為RadionButton 同一組中不可能同時選中!

好了除了樣式外功能實現完畢,當然還可以繼續拓展O(∩_∩)O哈哈~!

樣式

Expande樣式模板分為三個部分:

Expander-Header:一個寫好樣式的 ToggleButton。

Expander-Content: Border是內容部分  <ContentPresenter /> 這句話千萬不要遺忘,不然什麼都不會顯示!

Expander-Triggers: 動畫效果。

 

*這裡有個註意點,Header 與 Content 一定要放在 StackPanel 內,不然運行後Expander會重疊。 

樣式模板具體可以百度查詢,或者用Blend查看樣式源碼,這裡不再熬述。

        <!--ToggleButton樣式代碼:-->
        <Style x:Key="ToggleButtonStyle" TargetType="{x:Type ToggleButton}">
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Width" Value="{Binding Path=Width, ElementName=ItemBox}"/>
            <Setter Property="Height" Value="35" />
            <Setter Property="Background" Value="{StaticResource OrangeG}" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <Canvas  Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"        Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                            <Image  Source="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.ImgUrl}"  Height="16" Canvas.Left="5" Canvas.Top="8"  />
                            <TextBlock   Text="{Binding    RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=Expander},  Path=Tag.Title}"  Canvas.Left="25" Canvas.Top="8" Foreground="{TemplateBinding Foreground}"/>
                            <TextBlock Text=""  Foreground="White" Canvas.Top="6" Canvas.Right="10" FontSize="{TemplateBinding FontSize}" x:Name="txtSymbol"/>
                            <ContentPresenter />
                        </Canvas>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter Property="Background" Value="#3399ff"/>
                                <Setter Property="Text"  TargetName="txtSymbol" Value=""/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="False">
                                <Setter Property="Background" Value="{StaticResource OrangeG}"/>
                                <Setter Property="Text"  TargetName="txtSymbol" Value=""/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <!--Expander樣式代碼:-->
        <Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Expander}">
                        <StackPanel Background="{TemplateBinding Background}" >
                            <ToggleButton x:Name="HeaderSite"   Style="{DynamicResource ToggleButtonStyle}"
                               IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"  />
                            <Border x:Name="ExpandSite"   Visibility="Collapsed"   Canvas.Top="40"   Focusable="false"                  
                               BorderThickness="1"      Width="{Binding ElementName=HeaderSite,Path=Width}"  
                                    HorizontalAlignment="Center" >
                                <ContentPresenter    />
                            </Border>
                        </StackPanel>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsExpanded" Value="true">
                                <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

 最後調用樣式

    <Expander    Style="{DynamicResource ExpanderStyle}" 

這樣 簡易的手風琴就完成了,但是這裡還是有個小問題,就是內容模板的高度沒有辦法拉伸,以及WrapPanel內會出現左右邊距沒有辦法居中,如果有知道解決辦法的大神們請告知謝謝!!!

結束語

第一次寫博文,終於體會其中的奧妙之處,重新梳理下思路,對自己進行一次總結,進而增加深刻的印象,這種感覺棒棒噠,在這裡感謝為我指點的大神們,同時也希望大家為我指點其中的不足之處,進而改之,讓我可以快速的成長,再次感謝各位,謝謝!!!

(PS:話說怎麼樣可以讓博文的樣式更好看一些?)

這裡是源碼:https://git.oschina.net/smile0905/AccordionClient.git 


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

-Advertisement-
Play Games
更多相關文章
  • Shell 傳遞參數 我們可以在執行 Shell 腳本時,向腳本傳遞參數,腳本內獲取參數的格式為:$n。n 代表一個數字,1 為執行腳本的第一個參數,2 為執行腳本的第二個參數,以此類推…… 實例 以下實例我們向腳本傳遞三個參數,並分別輸出,其中 $0 為執行的文件名: 為腳本設置可執行許可權,並執行 ...
  • 1 補充知識 2 準備工作 3 Linux安裝JDK 4 Linux安裝MySQL 5 Linux安裝Tomcat ...
  • CMD命令大全:cmd.exe CMD命令提示符 ,gpedit.msc 組策略,chkdsk.exe Chkdsk磁碟檢查,regedit.exe 註冊表 等 cmd命令大全(第一部分) winver 檢查Windows版本 wmimgmt.msc 打開windows管理體繫結構(WMI) wup ...
  • 人們在互聯網上最常使用的就是電子郵件了,很多企業用戶也經常使用免費的電子郵件系統。今天我就給大家介紹一種在Red Hat Linux 9.0環境下運行的郵件伺服器軟體Sendmail.Sendmail作為一種免費的郵件伺服器軟體,已被廣泛的應用於各種伺服器中,它在穩定性、可移植性、及確保沒有bug等 ...
  • 沒玩過linux,折騰了半天的ftp,好不容易親測通過了。不容易啊。 操作環境:vm虛擬機 centos7 首先:搞定網路問題;預設情況下使用ifconfig可以看到虛擬機下是無網路的。(註:虛擬機網路設置為NAT或橋接模式都是可以的) 輸入命令nmtui 打開網路配置 回車-》回車 將倒數第二項 ...
  • 最近部署上線的一個引擎,啟動之後記憶體、日誌顯示一切正常,但是外部無法進行引擎訪問。幾經周折,在同事的協助下,找出了問題:root用戶的open files為1024,引擎啟動時,1024個文件句柄已經用盡。在晚上看到一篇不錯的文章,就轉下來了:http://jameswxx.iteye.com/bl ...
  • VopSdk一個高逼格微信公眾號開發SDK(源碼下載) VopSdk一個高逼格微信公眾號開發SDK:自動化生產(裝逼模式開啟) 針對第一版,我們搞了第二版本,老規矩先定個目標。 一 我們的目標 a、移除PayExcute,統一執行入口,目前只保留一個入口Excute b、序列化特性統一,目前只用設置 ...
  • ORM概念: 指對象結構和資料庫架構間的映射,對象和資料庫架構有一定的映射關係,讓程式員可以不必編寫sql ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...