HeyCHのブログ

慢性疲労のへいちゃんです

【C#】WPFって何よ

Windows Presentation Foundation (WPF) は、デスクトップ クライアント アプリケーションを作成する UI フレームワークです。
WPF の開発プラットフォームは、アプリケーション モデル、リソース、コントロール、グラフィックス、レイアウト、データ バインディング、ドキュメント、セキュリティなどのさまざまなアプリケーション開発機能の一式をサポートします。
(コピペ)

よく使う機能を簡単にまとめると、好きなようにレイアウトし、データバインディングでいい感じに描画できるのがWPFって感じです。
なんていうんですかね、TreeViewにTreeViewItemってあったじゃないですか、あれのItemをXAMLっていうので定義できるので、本当に自分の好きなようにレイアウトできるんですよね。(もちろん素のままでもOKです。)
なので、せっかくこんな便利な機能があるので、使わなきゃ損という事で、今回からWPFを勉強していこうと思います。

ストップウォッチをWPF化してみよう

以下の記事で書いたストップウォッチをWPF化してみようと思います。
heych.hatenablog.com

f:id:HeyCH:20200425000501p:plain
新しいプロジェクトの作成
f:id:HeyCH:20200425000558p:plain
WPF アプリ(.NET Framework
f:id:HeyCH:20200425000706p:plain
StopwatchWPF

<Window x:Class="StopwatcchWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StopwatcchWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Content="{Binding Time}" FontSize="24"/>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0" x:Name="button1" Content="Start"/>
            <Button Grid.Column="1" x:Name="button2" Content="Stop"/>
            <Button Grid.Column="2" x:Name="button3" Content="LAP"/>
        </Grid>
        <DataGrid Grid.Row="2" ItemsSource="{Binding LAPs}" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="時刻" Binding="{Binding Time}" IsReadOnly="True"/>
                <DataGridTextColumn Header="ラップタイム" Binding="{Binding LAPTime}" IsReadOnly="True"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
  • DataGridは AutoGenerateColumns="False"に設定しないと自動で列を作ってしまう。
  • また、DataGridを読取専用にしたい場合、列に対し、IsReadOnly="True"を設定する必要がある
  • Bindingの部分は自分で決める必要がある
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
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 StopwatcchWPF {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        ViewModel vm;
        DateTime now;
        DateTime startTime;
        BackgroundWorker bw;
        TimeSpan ts;
        List<TimeSpan> tsl = new List<TimeSpan>();
        enum StopwatchMode {
            Time,
            Stopwatch,
            Pause
        }
        StopwatchMode mode = StopwatchMode.Time;
        public MainWindow() {
            InitializeComponent();
            vm = new ViewModel();
            this.DataContext = vm;
            ts = new TimeSpan(0);
            bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }
        private void Bw_DoWork(object sender, DoWorkEventArgs e) {
            while (true) {
                now = DateTime.Now;
                if (mode == StopwatchMode.Stopwatch) {
                    ts = now - startTime;
                    try {
                        vm.Time = String.Format("{0:D2}:{1:D2}:{2:D2}.{3:D3}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
                    } catch (Exception ex) { }
                } else if (mode == StopwatchMode.Time) {
                    try {
                        vm.Time = String.Format("{0:D2}:{1:D2}:{2:D2}", now.Hour, now.Minute, now.Second, now.Millisecond);
                    } catch (Exception ex) { }
                } else if (mode == StopwatchMode.Pause) {
                    //何もしない
                }
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e) {
            if ((string)button1.Content == "Start") {
                if (mode != StopwatchMode.Pause) {
                    startTime = DateTime.Now;
                } else if (mode == StopwatchMode.Pause) {
                    startTime = DateTime.Now - ts;
                }
                mode = StopwatchMode.Stopwatch;
                button1.Content = "Pause";
            } else {
                mode = StopwatchMode.Pause;
                button1.Content = "Start";
            }
        }

        private void button2_Click(object sender, RoutedEventArgs e) {
            mode = StopwatchMode.Time;
            button1.Content = "Start";
        }

        private void button3_Click(object sender, RoutedEventArgs e) {
            if (mode != StopwatchMode.Stopwatch) return;
            tsl.Add(ts);
            TimeSpan tmp = new TimeSpan(0);
            if (tsl.Count <= 1) {
                tmp = tsl[tsl.Count - 1];
            } else {
                tmp = tsl[tsl.Count - 1] - tsl[tsl.Count - 2];
            }
            var lap = new LAP();
            lap.Time = String.Format("{0:D2}:{1:D2}:{2:D2}.{3:D3}", now.Hour, now.Minute, now.Second, now.Millisecond);
            lap.LAPTime = String.Format("{0:D2}:{1:D2}:{2:D2}.{3:D3}", tmp.Hours, tmp.Minutes, tmp.Seconds, tmp.Milliseconds);
            vm.LAPs.Add(lap);
        }
    }
    public class ViewModel : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        string _Time = "00:00:00.000";
        public string Time {
            get { return _Time; }
            set {
                _Time = value;
                RaisePropertyChanged("Time");
            }
        }
        ObservableCollection<LAP> _LAPs = new ObservableCollection<LAP>();
        public ObservableCollection<LAP> LAPs {
            get { return _LAPs; }
            set {
                _LAPs = value;
                RaisePropertyChanged("LAPs");
            }
        }
    }
    public class LAP {
        public string Time { get; set; }
        public string LAPTime { get; set; }
    }
}
  • ラベルの更新や、DataGridの更新には「PropertyChanged」イベントで通知する必要がある。
  • ListはAddしただけでは更新が通知されないため、「ObservableCollection」を使う必要がある。