[WPF][C#] 不要給我醜醜的空白列 - 讓DataGrid在沒資料的時候可以顯示自訂的內容

  • 16933
  • 0
  • C#
  • 2013-07-15

通常我們在使用DataGrid顯示資料的時候,大部份的情況下都會透過資料繫結(Data Binding)來完成,配合設計師設計出來美美的樣版,就能以比較有質感一點的方式見人(雖然說穿了還是一個DataGrid)。
不過有時候會遇到一個情況:當使用者操作資料,或進行查詢之後,發現繫結的對象已經被清空了,而DataGrid還是會很開心的以預設的樣版顯示一條空白列,對有的人來說,看了就是會覺得怪怪的。所以,這次就來跟大家分享一個可以在繫結的對象被清空的時候,還能顯示自訂內容的一個替代方法。

 

最近設計x程式社團的林星同學提到關於使用ObjectDataProvider來進行資料的繫結的方式,幫不上什麼大忙的我,只能寫些入門的小範例來看看能不能有所幫助,就透過這個小範例來讓有興趣的朋友們先對ObjectDataProvider有基本的認識吧!!

首先呢~請自行連結到MSDN上關於ObjectDataProvider的介紹看看先吧!!除此之外,MSDN上還有一個不錯的範例:How to: Bind to an Enumeration

如果有仔細看完MSDN的介紹,以及範例的內容,那應該對ObjectDataProvider有初步的了解了~

基本上,ObjectDataProvider的以下三個屬性是很值得拿來好好運用的喔!!

ObjectType:提供繫結資料的型別

MethodName:提供繫結資料的型別用來輸出資料的方法

MethodParameters:要傳給輸出資料的方法的參數

只要好好善用上面的三個屬性,就可以把之前很多得寫好幾行程式碼的東西,透過ObjectDataProvider,直接在Xaml裡面就做完喔!!暖身就到這邊告一段落啦!!

而今天要分享的範例呢,就如同文章標題所說的,要來透過C#在CodeBehind中改變ObjectDataProvider的資料來源!!讓我們先來看看實作出來的成品:

(*若無法正常瀏覽本文WPF版範例,煩請參考[Windows7]使用IE9、FireFox與Chrome瀏覽WPF Browser Application(.XBAP)的方式一文調整瀏覽器設定)

這個範例其實很簡單,我用C#先寫好兩個用來描述資料的簡單類別,分別為Member和Product,程式碼如下:

Member.cs
public class Member
{
    public int Id
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public bool Sex
    {
        get;
        set;
    }
}
Product.cs
public class Product
{
    public int Id
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public string OS
    {
        get;
        set;
    }

}

再來是兩個使用了上述兩個類別,可以透過呼叫裡面的方法,用來提供資料的類別:

Members.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class Members
{
    private ObservableCollection<Member> _members = new ObservableCollection<Member>
    {
        new Member{ Id =  1 , Name = "Ouch Liu" , Sex = true },
        new Member{ Id =  2 , Name = "Yuan Monkey" , Sex = false },
        new Member{ Id =  3 , Name = "Bill Gates" , Sex = true },
        new Member{ Id =  4 , Name = "Angela Baby" , Sex = false },
        new Member{ Id =  5 , Name = "Wei Liu" , Sex = true },
    };

    public List<Member> GetFemales()
    {
        return _members.Where( m => m.Sex == false ).ToList();
    }

    public List<Member> GetMales()
    {
        return _members.Where( m => m.Sex == true ).ToList();

    }
}
Products.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class Products
{
    private ObservableCollection<Product> _products = new ObservableCollection<Product>
    {
        new Product{ Id =  1 , Name = "LG GW910" , OS = "WP7" },
        new Product{ Id =  2 , Name = "SonyEricsson X10" , OS = "Android" },
        new Product{ Id =  3 , Name = "HTC HD7" , OS = "WP7" },
        new Product{ Id =  4 , Name = "iPhone 4" , OS = "iOS" },
        new Product{ Id =  5 , Name = "Samsung Focus" , OS = "WP7" },
    };

    public List<Product> GetWindowPhones()
    {
        return _products.Where( p => p.OS == "WP7" ).ToList();
    }

    public List<Product> GetAndroidPhones()
    {
        return _products.Where( p => p.OS == "Android" ).ToList();

    }
}

有了這四個類別之後,其實就可以在Xaml中直接透過ObjectDataProvider來呼叫Members.GetFemales()、Members.GetMales()或是Products.GetWindowPhones()、Products.GetAndroidPhone()這四個方法來取得資料,並進行繫結啦!!

不過我們要玩的不只是這樣,要在執行期動態的抽換資料的來源,就得在設定完ObjectDataProvider各個屬性之後,呼叫ObjectDataProvider.Refresh()方法,來看看上面的範例是怎麼寫的吧!!

Page1.xaml
<Page x:Class="Wpf_ObjectDataProvider.Page1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:Wpf_ObjectDataProvider"
      mc:Ignorable="d" 
      d:DesignHeight="600" d:DesignWidth="800" ShowsNavigationUI="False"
      Title="Page1">
	<Border x:Name="LayoutRoot" Background="White" BorderBrush="#FF646464" 
			BorderThickness="2" CornerRadius="10" Margin="10">
		<Border.Resources>
			<ObjectDataProvider x:Key="dataSource" ObjectType="{x:Type local:Members}" MethodName="GetMales" />
		</Border.Resources>
		<Grid>
			<Grid.RowDefinitions>
				<RowDefinition Height="40"/>
				<RowDefinition Height="*"/>
				<RowDefinition Height="Auto"/>
			</Grid.RowDefinitions>
			<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap"
				Text="WPF ObjectDataProvider 範例" VerticalAlignment="Center" FontSize="26.667"
				FontWeight="Bold" Foreground="#FF646464" />
			<Rectangle Fill="#FF646464" Height="2" Margin="10,0" VerticalAlignment="Bottom" />
			<DataGrid x:Name="grdSample" Grid.Row="1" Margin="10" IsReadOnly="True" AutoGenerateColumns="True" 
					  ItemsSource="{Binding Source={StaticResource dataSource}}" FontSize="14.667"/>
			<StackPanel Orientation="Horizontal" Grid.Row="2" HorizontalAlignment="Center" 
						VerticalAlignment="Center" Margin="0,0,0,10">
				<TextBlock TextWrapping="Wrap" Text="ObjectType:" VerticalAlignment="Center" FontSize="14.667" 
						   Foreground="#FF646464" FontWeight="Bold"/>
				<ComboBox x:Name="cmbType" VerticalAlignment="Center" SelectedIndex="0" Width="150" 
						  SelectionChanged="cmbType_SelectionChanged">
					<ComboBoxItem Content="Members"/>
					<ComboBoxItem Content="Products"/>
				</ComboBox>
				<TextBlock TextWrapping="Wrap" Text="MethodName:" VerticalAlignment="Center" FontSize="14.667" 
						   Foreground="#FF646464" Margin="20,0,0,0" FontWeight="Bold"/>
				<ComboBox x:Name="cmbMembers" VerticalAlignment="Center" SelectedIndex="0" Width="150">
					<ComboBoxItem Content="GetMales"/>
					<ComboBoxItem Content="GetFemales"/>
				</ComboBox>
				<ComboBox x:Name="cmbProducts" VerticalAlignment="Center" SelectedIndex="0" Width="150" Visibility="Collapsed">
					<ComboBoxItem Content="GetWindowPhones"/>
					<ComboBoxItem Content="GetAndroidPhones"/>
				</ComboBox>
				<Button x:Name="btnUpdateDataSource" Content="更新資料源" FontSize="13.333" Padding="10,1" Margin="10,0,0,0"
                        Click="btnUpdateDataSource_Click" />
			</StackPanel>
		</Grid>
	</Border>
</Page>
Page1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Wpf_ObjectDataProvider
{
    public partial class Page1 : Page
    {
        private ObjectDataProvider _objectDataProvider;

        public Page1()
        {
            InitializeComponent();

            _objectDataProvider = this.LayoutRoot.FindResource( "dataSource" ) as ObjectDataProvider;
        }

        private void cmbType_SelectionChanged( object sender , SelectionChangedEventArgs e )
        {
            if( this.cmbMembers == null || this.cmbProducts == null )
            {
                return;
            }

            this.cmbMembers.Visibility = System.Windows.Visibility.Collapsed;
            this.cmbProducts.Visibility = System.Windows.Visibility.Collapsed;

            switch( (cmbType.SelectedItem as ComboBoxItem).Content.ToString() )
            {
                case "Members":
                    this.cmbMembers.Visibility = System.Windows.Visibility.Visible;
                    break;
                case "Products":
                    this.cmbProducts.Visibility = System.Windows.Visibility.Visible;
                    break;
            }

        }

        private void btnUpdateDataSource_Click( object sender , RoutedEventArgs e )
        {
            switch( ( cmbType.SelectedItem as ComboBoxItem ).Content.ToString() )
            {
                case "Members":
                    _objectDataProvider.ObjectType = typeof( Members );
                    _objectDataProvider.MethodName = ( cmbMembers.SelectedValue as ComboBoxItem ).Content.ToString();
                    break;
                case "Products":
                    _objectDataProvider.ObjectType = typeof( Products );
                    _objectDataProvider.MethodName = ( cmbProducts.SelectedValue as ComboBoxItem ).Content.ToString();
                    break;
            }

            this._objectDataProvider.Refresh() ;

            if( _objectDataProvider.Error != null )
            {
                MessageBox.Show( _objectDataProvider.Error.Message );
            }

        }
    }
}

就這麼簡單,一點都不難吧!!而在使用ObjectDataProvider的時候,如果像上面的範例一樣,會有兩種不同的資料來源類別,而用來呈現資料的元件又不是DataGrid的話,就得配合相對應的DataTemplate,才能呈現不同類別的資料喔!!(當然也可以再把上面的範例改成透過MethodPrameters來做得更漂亮喔~不過這個就留給大家自己練習啦!!)

 

最後,一樣奉上專案原始碼,請自行服用: