WPFでDataGridの実装
今回はC#でWPFアプリケーションの制作にポンコツ2人組が挑戦してみました。 APIから取得したデータをDataGridに表示するアプリケーションです。 開発環境はVisualStudio2019で、ライブラリはNewtonsoft.Jsonを使用しています。C#初心者が制作したものなので いろいろとおかしい部分が多いと思います。プログラムを流用する際は適宜修正してください。
App.xaml
ブール値から Visibility 列挙値への変換や、その逆の変換を行うコンバーター(BooleanToVisibilityConverter)と
IValueConverterインターフェースを実装したクラス(InverseBooleanConverter(ソースは下の方にあります))を
Application.Resourcesに追加しています。
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<local:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
</Application.Resources>
</Application>
App.xaml.cs
このソースは特に何もしていません。デフォルトのままです。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1
{
///
/// Interaction logic for App.xaml
///
public partial class App : Application
{
}
}
MainWindow.xaml
データグリッドに表示するデータを取得するためにAPIにリクエストを送るボタンと、
データグリッドで選択した行のデータをテキストエリアに表示するボタン、そしてデータグリッドを配置しています。
データグリッドの行をクリックするごとに選択状態がON・OFFされます。
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:av="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="av" x:Class="WpfApp1.MainWindow"
Title="API Data Display" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="491*"/>
<ColumnDefinition Width="309*"/>
</Grid.ColumnDefinitions>
<Button Content="APIにリクエスト" Command="{Binding LoadDataCommand}" IsEnabled="{Binding IsLoading, Converter={StaticResource InverseBooleanConverter}}" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="150,15,0,0" Width="87"/>
<Button Content="選択されているデータを出力" Click="LogSelectedData_Click" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="450,15,0,0" Grid.ColumnSpan="2" Width="142"/>
<TextBlock x:Name="logTextBlock" Margin="10,10,360,10" FontSize="16"/>
<DataGrid x:Name="dataGrid" IsReadOnly="True" ItemsSource="{Binding DataItems}" AutoGenerateColumns="False" CanUserAddRows="False" HorizontalAlignment="Left" Width="500" Grid.ColumnSpan="2" Margin="150,40,0,20">
<DataGrid.Resources>
<!-- ヘッダースタイルをカスタマイズ -->
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="30"/>
</Style>
<local:BooleanToIconConverter x:Key="BooleanToIconConverter"/>
<!-- コンバーターをリソースに追加 -->
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Height" Value="50"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="FontSize" Value="40"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Select">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="PreviewMouseDown" Handler="Cell_PreviewMouseDown"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderThickness" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding IsSelected, Converter={StaticResource BooleanToIconConverter}}" Width="50" Height="50"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="400">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="PreviewMouseDown" Handler="Cell_PreviewMouseDown"/>
<Setter Property="FontSize" Value="40"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="BorderThickness" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="Loading..." Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="400,0,0,0"/>
</Grid>
</Window>
MainWindow.xaml.cs
データグリッドの行をチェックした時の処理と、選択した行のデータをテキストエリアに出力する処理を実装しています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WpfApp1
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Cell_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is DataGridCell cell)
{
if (cell.DataContext is ApiDataItem dataItem)
{
dataItem.IsSelected = !dataItem.IsSelected; // チェックボックスと連動
}
}
}
private void LogSelectedData_Click(object sender, RoutedEventArgs e)
{
string logText = "";
foreach (ApiDataItem selectedItem in dataGrid.Items)
{
if (selectedItem.IsSelected)
{
logText += $"{selectedItem.Name}\n";
}
}
logTextBlock.Text = logText; // TextBlock にログを設定
}
}
}
BooleanToIconConverter.cs
データグリッドの選択行のレ点チェック画像をチェックあり・なしの画像で切り替えています。
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace WpfApp1
{
public class BooleanToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
string iconName = boolValue ? "checked_icon.png" : "unchecked_icon.png";
return new BitmapImage(new Uri($"/WpfApp1;component/image/{iconName}", UriKind.RelativeOrAbsolute));
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
InverseBooleanConverter.cs
IValueConverterインターフェースを実装したクラスです。
プロパティ→画面表示・画面表示→プロパティの双方向の変換を記述するようです。
ここでは前者のみ実装し、後者はエラーとしています。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows;
namespace WpfApp1
{
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return !boolValue;
}
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
ApiDataItem.cs
APIから取得したデータを格納するクラス(ApiDataItem)、APIにリクエストを送信して取得したデータを
画面と連携させるためのクラス(ViewModel)、画面のAPIにリクエストボタンをクリックした際に
トリガーとなるICommandを実装したクラス(RelayCommand)です。
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApp1
{
public class ApiDataItem : INotifyPropertyChanged
{
private bool _isSelected;
private string? _name;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public string? Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ApiDataItem>? _dataItems;
private bool _isLoading;
private RelayCommand? _loadDataCommand;
public ObservableCollection<ApiDataItem>? DataItems
{
get { return _dataItems; }
set
{
_dataItems = value;
OnPropertyChanged();
}
}
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged();
}
}
public RelayCommand? LoadDataCommand
{
get
{
return _loadDataCommand ??= new RelayCommand(async () =>
{
try
{
IsLoading = true;
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("http://localhost:8080/api/sample");
if (response.IsSuccessStatusCode)
{
var jsonResponse = await response.Content.ReadAsStringAsync();
DataItems = JsonConvert.DeserializeObject<ObservableCollection<ApiDataItem>>(jsonResponse);
}
else
{
// Handle API error
}
}
catch (Exception ex)
{
// Handle exception
Console.WriteLine(ex.ToString());
}
finally
{
IsLoading = false;
}
});
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RelayCommand : ICommand
{
private readonly Action? _action;
public RelayCommand(Action? action)
{
_action = action;
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_action?.Invoke();
}
public event EventHandler? CanExecuteChanged;
}
}
これらのソースはnamespace(WpfApp1)の直下に配置しており、 画像ファイル(checked_icon.pngとunchecked_icon.png)はnamespace(WpfApp1)直下のimageフォルダにリソースとして登録しています。 (画像ファイルやAPIのソースは当記事に掲載していませんのでご自身でご用意ください。)
以下がアプリケーションを実行した画面です。