Reprint - Code implementation - Use The List View
28762 단어 C# WPF ListView
WPF Tutorial - Using ListView, Part I
WPF Tutorial - Using ListView, Part II
WPF Tutorial - Using ListView, Part III
The topic cover from the basic of ListView capability to more advanced and more customized ones such as define the Sorting and Inline editing of ListViews.
There is a lots of code, which I would recommend that you read along their orginal blogs. Below is the code with my own connotation in the code.
ListView basic
Below is the code that demonstrate the basic use of ListViews.
xaml part.
<!-- you can either set the DataContext to itself, with the following code -->
<!--<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
>-->
<!-- or you give a name to yourself, in this case, the name is "This"
later, you can bind to "This"-->
<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:ListView1"
Title="MainWindow" Height="350" Width="525"
x:Name="This"
>
<StackPanel>
<ListView>
<ListView.View>
<!-- For ListView, to have it to show as the Grid based view, the key is to Listview.View
and you will need to nest the GridView into the ListView.View
-->
<GridView>
<GridViewColumn Width="140" Header="1" />
<GridViewColumn Width="140" Header="2" />
<GridViewColumn Width="140" Header="3" />
</GridView>
</ListView.View>
</ListView>
<!-- below shows how bind different column to the data field (DisplayMember) -->
<ListView>
<ListView.View>
<GridView>
<!-- data to this column won't need a DisplayMember attribute because if it is omitted,
then it is the whole data that get displayed -->
<GridViewColumn Width="120" Header="Date" />
<GridViewColumn Width="120" Header="Day of Week"
DisplayMemberBinding="{Binding DayOfWeek}"
/>
<GridViewColumn Width="120" Header="Year" DisplayMemberBinding="{Binding Year}" />
</GridView>
</ListView.View>
<sys:DateTime>1/2/3</sys:DateTime>
<sys:DateTime>4/5/6</sys:DateTime>
<sys:DateTime>7/8/9</sys:DateTime>
<sys:DateTime>10/11/12</sys:DateTime>
</ListView>
<!-- this is yet another example, which shows how to bind to data and etc...
SO far so good, let dive into how to sort ListView and etc..
-->
<!-- this is when you have datacontex set to itself -->
<!--<ListView
x:Name="gameListView" ItemsSource="{Binding Path=GameCollection}"
>-->
<!-- This is when you have already assigned a name to the top level control (The window control) -->
<ListView
x:Name="gameListView" ItemsSource="{Binding ElementName=This, Path=GameCollection}"
>
<ListView.View>
<GridView>
<GridViewColumn Width="140" Header="Game Name"
DisplayMemberBinding="{Binding GameName}" />
<GridViewColumn Width="140" Header="Creator"
DisplayMemberBinding="{Binding Creator}" />
<GridViewColumn Width="140" Header="Publisher"
DisplayMemberBinding="{Binding Publisher}" />
</GridView>
</ListView.View>
</ListView>
<Button HorizontalAlignment="Right" Margin="5,5,5,5" Content="Add Row" Click="AddRow_Click" />
</StackPanel>
</Window>
the C# parts.
namespace ListView1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeMainWindow();
}
#region Data
public class GameData
{
public string GameName { get; set; }
public string Creator { get; set; }
public string Publisher { get;set; }
}
ObservableCollection<GameData> _GameCollection = new ObservableCollection<GameData>();
public void InitializeMainWindow()
{
// This will initialize a load of data into the _GameCollection
_GameCollection.Add(new GameData
{
GameName = "World Of Warcraft",
Creator = "Blizzard",
Publisher = "Blizzard"
});
_GameCollection.Add(new GameData
{
GameName = "Halo",
Creator = "Bungie",
Publisher = "Microsoft"
});
_GameCollection.Add(new GameData
{
GameName = "Gears of War",
Creator = "Epic",
Publisher = "Microsoft"
});
}
// expose the ObservableCollection as Property that you can bind to
public ObservableCollection<GameData> GameCollection
{
get { return _GameCollection; }
}
#endregion Data
}
}
ListView Sorting
Below show the code that has implement the sorting within ListView.
the .xaml parts
<!-- you can either set the DataContext to itself, with the following code -->
<!--<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
>-->
<!-- or you give a name to yourself, in this case, the name is "This"
later, you can bind to "This"-->
<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:ListView1"
Title="MainWindow" Height="350" Width="525"
x:Name="This"
>
<StackPanel>
<!-- while the abovce code is a non-sortable version, below will utilize the sorting function -->
<!-- ... Omiss of the irrelevant part --><Button HorizontalAlignment="Right" Margin="5,5,5,5" Content="Add Row" Click="AddRow_Click" />
<ListView
x:Name="gameListView2" ItemsSource="{Binding ElementName=This, Path=GameCollection}"
>
<ListView.View>
<GridView>
<GridViewColumn Width="140"
DisplayMemberBinding="{Binding GameName}">
<!-- in this example, the Tag property holds some data, the data being the name of
backing field that we want to sort on -->
<GridViewColumnHeader Click="SortClick"
Tag="GameName"
Content="Game Name">
</GridViewColumnHeader>
</GridViewColumn>
<GridViewColumn Width="140"
DisplayMemberBinding="{Binding Creator}">
<GridViewColumnHeader Click="SortClick"
Tag="Creator"
Content="Creator">
</GridViewColumnHeader>
</GridViewColumn>
<GridViewColumn Width="140"
DisplayMemberBinding="{Binding Publisher}">
<GridViewColumnHeader Click="SortClick"
Tag="Publisher"
Content="Publisher">
</GridViewColumnHeader>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
And the C# code part.
namespace ListView1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeMainWindow();
}
#region Sorting
private GridViewColumnHeader _CurSortCol = null;
private SortAdorner _CurAdorner = null;
// Can we apply the style on the ListView, is the Adorner layer the only option?
//
private void SortClick(object sender, RoutedEventArgs e)
{
GridViewColumnHeader column = sender as GridViewColumnHeader;
String field = column.Tag as string;
// now SortClick only works with the gameListVIew2,
// you can leverage the VisualTreeHelper to look for
// the LiveView.
// below show the code to find the parent
var parent = column.Parent;
while (parent != null)
{
if (parent is ListView)
{
break;
}
parent = VisualTreeHelper.GetParent(parent);
}
if (_CurSortCol != null)
{
// if the current column is not null, we remove the previous adorner
AdornerLayer.GetAdornerLayer(_CurSortCol).Remove(_CurAdorner);
//gameListView2.Items.SortDescriptions.Clear();
((ListView)parent).Items.SortDescriptions.Clear();
}
ListSortDirection newDir = ListSortDirection.Ascending;
if (_CurSortCol == column && _CurAdorner.Direction == newDir)
newDir = ListSortDirection.Descending;
_CurSortCol = column;
_CurAdorner = new SortAdorner(_CurSortCol, newDir);
AdornerLayer.GetAdornerLayer(_CurSortCol).Add(_CurAdorner);
//gameListView2.Items.SortDescriptions.Add(new SortDescription(field, newDir));
((ListView)parent).Items.SortDescriptions.Add(new SortDescription(field, newDir));
}
// you may take reference to the http://www.switchonthecode.com/tutorials/wpf-tutorial-using-the-listview-part-2-sorting
// for more illustration on the Adorners
public class SortAdorner : Adorner
{
private readonly static Geometry _AscGeometry = Geometry.Parse("M 0,0 L 10,0 L 5,5 Z");
private readonly static Geometry _DescGeometry = Geometry.Parse("M 0,5 L 10,5 L 5,0 Z");
// keep record of the current sorting direction
public ListSortDirection Direction { get; private set; }
public SortAdorner(UIElement element, ListSortDirection dir)
: base(element)
{
Direction = dir;
}
// the significant part of the adorner is the OnRender method,
// you will need to add the triangle to indica the direction of the sorting
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
// do nothing if decorated element is less than 20 in width
if (AdornedElement.RenderSize.Width < 20)
return;
// why we need to apply the Transformation with the Push and Pop method ??
// is that because we can push something while the transform still affect others down
// in the stream?
drawingContext.PushTransform(
new TranslateTransform(
AdornedElement.RenderSize.Width - 15,
(AdornedElement.RenderSize.Height - 5) / 2));
drawingContext.DrawGeometry(
Brushes.Black, null, Direction == ListSortDirection.Ascending ? _AscGeometry : _DescGeometry);
drawingContext.Pop();
}
}
private void AddRow_Click(object sender, RoutedEventArgs e)
{
_GameCollection.Add(new GameData
{
GameName = "A New Game",
Creator = "A New Creator",
Publisher = "A New Publisher"
}
);
}
#endregion Sorting
}
}
ListView Inline Edit
The xaml part of the code
<!-- you can either set the DataContext to itself, with the following code -->
<!--<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
>-->
<!-- or you give a name to yourself, in this case, the name is "This"
later, you can bind to "This"-->
<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:ListView1"
Title="MainWindow" Height="350" Width="525"
x:Name="This"
>
<StackPanel>
<ListView
x:Name="editableGameListView2"
ItemsSource="{Binding ElementName=This, Path=BindableGameCollection}"
>
<!-- Define Resource that shall be used -->
<ListView.Resources>
<!--
Here you have to define the Customer Converter
-->
<local:BoolToVisibilityConverter x:Key="boolToVis" />
<Style TargetType="{x:Type TextBlock}"
x:Key="GridBlockStyle" >
<Setter Property="VerticalAlignment" Value="Center" />
<!-- the TextBlock or the TextBox within are both embedded inside the ListViewItem element -->
<Setter Property="Visibility"
Value="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis},ConverterParameter=false}" />
</Style>
<!--
You may wonder why we don't put the TargetType of GridEditStyle?
The reason is because we are going to apply the GridEditStyle on both the TextBox and the ComboBox
So we have to choose the common base (denominator), here in this case, their common case are FrameElement
So it demonstrate that you can create some base types and applies to the child Element.
-->
<Style TargetType="{x:Type FrameworkElement}"
x:Key="GridEditStyle">
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<!-- there is a converter parameter, which will invert the Visibility of Boolean2Visibility -->
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}},
Converter={StaticResource boolToVis}, ConverterParameter=true}"></Setter>
</Style>
</ListView.Resources>
<!-- define the view of the ListView-->
<ListView.View>
<GridView>
<GridViewColumn Width="140">
<GridViewColumnHeader Click="SortClick"
Tag="GameName"
Content="Game Name" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=GameName}"
Style="{StaticResource GridBlockStyle}" />
<TextBox Text="{Binding Path=GameName}"
Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="140">
<GridViewColumnHeader Click="SortClick"
Tag="Creator"
Content="Creator" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Creator}"
Style="{StaticResource GridBlockStyle}" />
<TextBox Text="{Binding Path=Creator}"
Style="{StaticResource GridEditStyle}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="140">
<GridViewColumnHeader Click="SortClick"
Tag="Publisher"
Content="Publisher" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- why we want to have the Grid's View style to be Strech?? -->
<Grid
HorizontalAlignment="Stretch"
>
<!-- While for the
Publisher, we will use a ComboBox to let you choose the right publisher
-->
<TextBlock Text="{Binding Path=Publisher}"
Style="{StaticResource GridBlockStyle}" />
<ComboBox Text="{Binding Path=Publisher}"
Style="{StaticResource GridEditStyle}"
ItemsSource="{Binding ElementName=This, Path=AvailablePublishers}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button Content="Add Row" Click="AddBindableRowClick" HorizontalAlignment="Right" Margin="5" />
</StackPanel>
</Window>
And the C# parts.
namespace ListView1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeMainWindow();
InitializeBindableGameData();
}
#region Inline Editing
// Comparing ot the GameData which is a plain data object
// the BindableGameData is a class that you can bind to UI
// it
// But, however, why do we need to make the BindableGameData
// a subsclass of the DependencyObject?
//
// QUESTION:
// you may want to find out the difference between using the DependencyObject and the INotifyPropertyChanged interface
//
// https://channel9.msdn.com/Forums/TechOff/466194-ViewModel--INotifyPropertyChanged-or-DependencyObject
// http://blog.quantumbitdesigns.com/2010/01/26/mvvm-lambda-vs-inotifypropertychanged-vs-dependencyobject/
public class BindableGameData : DependencyObject
{
public string GameName
{
get { return (string)GetValue(GameNameProperty); }
set { SetValue(GameNameProperty, value); }
}
// Using a DependencyProperty as the backing store for GameName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GameNameProperty =
DependencyProperty.Register("GameName", typeof(string), typeof(BindableGameData), new UIPropertyMetadata(null));
public string Creator
{
get { return (string)GetValue(CreatorProperty); }
set { SetValue(CreatorProperty, value); }
}
// Using a DependencyProperty as the backing store for Creator. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CreatorProperty =
DependencyProperty.Register("Creator", typeof(string), typeof(BindableGameData), new UIPropertyMetadata(null));
public string Publisher
{
get { return (string)GetValue(PublisherProperty); }
set { SetValue(PublisherProperty, value); }
}
// Using a DependencyProperty as the backing store for Publisher. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PublisherProperty =
DependencyProperty.Register("Publisher", typeof(string), typeof(BindableGameData), new UIPropertyMetadata(null));
}
private void AddBindableRowClick(object sender, RoutedEventArgs e)
{
_BindableGameCollection.Add(new BindableGameData
{
GameName = "A New Game",
Creator = "A New Creator",
Publisher = "<Select A Publisher",
}
);
}
private ObservableCollection<BindableGameData> _BindableGameCollection = new ObservableCollection<BindableGameData>();
private ObservableCollection<string> _AvailablePublishers = new ObservableCollection<string>();
public ObservableCollection<BindableGameData> BindableGameCollection
{
get { return _BindableGameCollection; }
}
public ObservableCollection<string> AvailablePublishers
{
get { return _AvailablePublishers; }
}
private void InitializeBindableGameData()
{
_BindableGameCollection.Add(new BindableGameData
{
GameName = "World Of Warcraft",
Creator = "Blizzard",
Publisher = "Blizzard"
});
_BindableGameCollection.Add(new BindableGameData
{
GameName = "Halo",
Creator = "Bungie",
Publisher = "Microsoft"
});
_BindableGameCollection.Add(new BindableGameData
{
GameName = "Gears Of War",
Creator = "Epic",
Publisher = "Microsoft"
});
_AvailablePublishers.Add("Microsoft");
_AvailablePublishers.Add("Blizzard");
_AvailablePublishers.Add("Nintendo");
_AvailablePublishers.Add("Electronic Arts");
_AvailablePublishers.Add("Activision");
_AvailablePublishers.Add("Ubisoft");
_AvailablePublishers.Add("Take-Two Interactive");
}
#endregion Inline Editing
}
#region Inline Editing Converter(s)
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var val = (bool)value;
var param = bool.Parse(parameter as string);
return val == param ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
#endregion Inline Editing Converter(s)
}