Counter (C#、WPF)

職場の若手向けお勉強テーマとして作成しました。C#(WPF) で下記の機能を実現しなさい、という内容です。
カウンタ表示部と、内部変数、をデータバインディングします。

 

 

 

 

1. 要求仕様

1.1. 機能

 

1.2. 画面仕様

画面仕様は以下の通り。記載事項以外は自由に設計のこと。

画面仕様

項番

概要

コントロール

タブオーダー

文字列、文字種別、他

 

-

 

“Counter”

Windowサイズ:800×600以内

値表示部

TextBox

(*1)

Number, 値範囲:010

フォントサイズ:36pt

センター表示

ReadOnly

+1

Button

1

“+”

フォントサイズ:12pt

-1

Button

2

“-“

フォントサイズ:12pt

0クリア

Button

3

“Clear”

フォントサイズ:12pt

プログラム

終了ボタン

Button

0

“Quit”

フォントサイズ:12pt

(*1) TabStop を False に設定

 

1.3.実装仕様(C#)

 

1.4.動作環境、開発環境

動作環境: Windows XP または Windows7
開発環境: Visual Studio 2008 以降
開発言語: C#(WPF)

 

 

2. 実装例(その1): TextBox をモデルに結び付ける

実際に私が作成したプログラムの画面は下図の通り。
TextBoxの部分だけ 色づけ を遊んでみました。パネルが曲面を描いているように見えれば幸いです。
実作業は Expression Blend で行いました。Blendが無いとWPFの実装は面白くないですね。

作成したプログラム画面

 

WPF では、XAMLとC#コード間のデータバインディングを行うために "DataContext" というメンバ変数を介して行うようです。

[プログラムソース (Window1.xaml)]

<Window
  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" mc:Ignorable="d"
  xmlns:CounterDll="clr-namespace:CounterDll;assembly=CounterDll" x:Class="CounterWPF.Window1"
  Title="Counter on the WPF" Height="237.333" Width="347.467"
  ResizeMode="NoResize" Background="White" Closing="Window_Closing">

  <Grid>
    <Button x:Name="Button_Quit" HorizontalAlignment="Right" Margin="8,0,8,8" Width="100"
      Content="Quit" VerticalAlignment="Bottom" Height="50" FontFamily="Arial" FontSize="16"
      Click="Button_Click_Quit" ToolTip="プログラムを終了します。" />

    <TextBox x:Name="TextBox_CounterView" Margin="8,8,8,0" VerticalAlignment="Top" Height="59"
      TextWrapping="Wrap" FontFamily="Arial" FontSize="48" TextAlignment="Center"
      Text="{Binding Path=counter.CountValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
      IsReadOnly="True" IsTabStop="False" ToolTip="値を表示します。">
      <TextBox.Background>
        <LinearGradientBrush EndPoint="0.496,0.022" StartPoint="0.498,0.936">
          <GradientStop Color="#FF8392AD" Offset="0.004"/>
          <GradientStop Color="#FFE2E5EA" Offset="1"/>
        </LinearGradientBrush>
      </TextBox.Background>
      <TextBox.Effect>
        <DropShadowEffect Direction="135" ShadowDepth="2"/>
      </TextBox.Effect>
    </TextBox>

    <Button x:Name="Button_Increment" HorizontalAlignment="Left" Margin="8,80,0,0" Width="100"
      Content="+" FontFamily="Arial" FontSize="16" Click="Button_Click_Increment" 
      ToolTip="値を1つ加算します。" Height="50" VerticalAlignment="Top" />
    <Button x:Name="Button_Decrement" Margin="0,80,0,0" Content="-" FontFamily="Arial" FontSize="16"
      Click="Button_Click_Decrement" ToolTip="値を1つ減算します。" Width="100" 
      HorizontalAlignment="Center" VerticalAlignment="Top" Height="50" />
    <Button x:Name="Button_Clear" Margin="0,80,8,0" Content="Clear" HorizontalAlignment="Right"
      Width="100" FontFamily="Arial" FontSize="16" Click="Button_Click_Clear" 
      ToolTip="値を0にクリアします。" Height="50" VerticalAlignment="Top" />
    <Rectangle Fill="White" Margin="8,14.6,8,0" VerticalAlignment="Top" Height="13.6" Opacity="0.85">
      <Rectangle.Effect>
        <BlurEffect Radius="10"/>
      </Rectangle.Effect>
    </Rectangle>
  </Grid>
</Window>

 

[プログラムソース (Window1.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;

using CounterDll;


namespace CounterWPF
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        private Counter _counter = new Counter();


        public Window1()
        {
            InitializeComponent();
//          DataContext = _counter;
            DataContext = new
            {
                counter = _counter,
            };
            Button_Click_Clear(this, new RoutedEventArgs());
        }

        private void Button_Click_Quit(object sender, System.Windows.RoutedEventArgs e)
        {
            // TODO: ここにイベント ハンドラーのコードを追加します。
            Close();
        }

        private void Button_Click_Increment(object sender, System.Windows.RoutedEventArgs e)
        {
            _counter.Increment();

            if (_counter.CountValue == _counter.CountMax)
            {
                Button_Increment.IsEnabled = false;
            }
            if (_counter.CountValue != _counter.CountMin)
            {
                Button_Decrement.IsEnabled = true;
            }
        }

        private void Button_Click_Decrement(object sender, System.Windows.RoutedEventArgs e)
        {
            _counter.Decrement();

            if (_counter.CountValue != _counter.CountMax)
            {
                Button_Increment.IsEnabled = true;
            }
            if (_counter.CountValue == _counter.CountMin)
            {
                Button_Decrement.IsEnabled = false;
            }
        }

        private void Button_Click_Clear(object sender, System.Windows.RoutedEventArgs e)
        {
            _counter.Clear();
            Button_Increment.IsEnabled = true;
            Button_Decrement.IsEnabled = false;
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (MessageBox.Show("プログラムを終了してよろしいですか?", "終了確認",
                  MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                // プログラムを終了する
            }
            else
            {
                // プログラムを終了しない
                e.Cancel = true;
            }
        }
    }
}

 

[プログラムソース (Counter.cs)]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ComponentModel; // INotifyPropertyChanged


[assembly: CLSCompliant(true)]  // CA1014
namespace CounterDll
{
    public class Counter : INotifyPropertyChanged
    {

        private int _countValue ;
        public int CountValue
        {
            get
            {
                return _countValue;
            }
            set
            {
                _countValue = value;
                NotifyPropertyChanged("CountValue");
            }
        }

        private readonly int _CountMax = 10;
        public int CountMax
        {
            get
            {
                return _CountMax;
            }
        }

        private readonly int _CountMin = 0;
        public int CountMin
        {
            get
            {
                return _CountMin;
            }
        }
    
        public void Increment()
        {
            if (_countValue < _CountMax)
            {
                CountValue++;
            }
        }

        public void Decrement()
        {
            if (_countValue > _CountMin)
            {
                CountValue--;
            }
        }

        public void Clear()
        {
            CountValue = 0;
        }


        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion // INotifyPropertyChanged

    }
}

サンプルプログラム ダウンロード

 

 

3. 実装例(その2): Button をコマンドに結び付ける

BUTTON のコマンドをモデル部に結び付けます。
ここでは(その1)からの改造ではなくて新規作成のイメージで進めます。理由は、この手順で進めた場合にはコントロールへのデータ・バインディングがVSからGUIで実施しやすくなることを示すためです。
以下にプログラムを記載します。

 

3.1. Counter.dll

class Counter をDLLとして作成します。
DLLでなくても動作するはずです。

["Counter.cs"]

using System;

[assembly: CLSCompliant(true)]  // CA1014
namespace CounterDll
{
    public class Counter : ViewModel
    {
        #region ICommands
        // +ボタンで呼ばれるコマンド
        public DelegateCommand IncrementCommand { get; private set; }

        // -ボタンで呼ばれるコマンド
        public DelegateCommand DecrementCommand { get; private set; }

        // Clearボタンで呼ばれるコマンド
        public DelegateCommand ClearCommand { get; private set; }
        #endregion ICommand


        #region countValue
        private int _countValue ;
        public int CountValue
        {
            get
            {
                return _countValue;
            }
            set
            {
                _countValue = value;
                NotifyPropertyChanged("CountValue");
                IncrementCommand.NotifyCanExcuteChanged();
                DecrementCommand.NotifyCanExcuteChanged();
            }
        }

        private readonly int _CountMax = 10;
        public int CountMax
        {
            get
            {
                return _CountMax;
            }
        }

        private readonly int _CountMin = 0;
        public int CountMin
        {
            get
            {
                return _CountMin;
            }
        }
        #endregion countValue


        /// <summary>
        /// コンストラクタ
        /// </summary>
        public Counter()
        {
            // + コマンドを実装
            IncrementCommand = new DelegateCommand(
                () => {
                    Increment();
                },
                () =>
                {
                    bool bCanExecute = true;

                    if ( CountValue >= CountMax)
                    {
                        bCanExecute = false;
                    }

                    return bCanExecute;
                });

            // - コマンドを実装
            DecrementCommand = new DelegateCommand(
                () => {
                    Decrement();
                },
                () =>
                {
                    bool bCanExecute = true;

                    if (CountValue <= CountMin)
                    {
                        bCanExecute = false;
                    }

                    return bCanExecute;
                });

            // Clear コマンドを実装
            ClearCommand = new DelegateCommand(
                () =>
                {
                    Clear();
                });
        }

        private void Increment()
        {
            if (_countValue < _CountMax)
            {
                CountValue++;
            }
        }

        private void Decrement()
        {
            if (_countValue > _CountMin)
            {
                CountValue--;
            }
        }

        private void Clear()
        {
            CountValue = 0;
        }
    }
}

 

[ViewModel.cs]

using System.ComponentModel;            // INotifyPropertyChanged
using System.Runtime.CompilerServices;  // CallerMemberName

namespace CounterDll
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

 

[DelegateCommand.cs]

using System;
using System.Windows.Input;             // ICommand

namespace CounterDll
{

    public class DelegateCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        public DelegateCommand(Action execute)
            : this(execute, () => true)
        { }

        public DelegateCommand(Action execute, Func<bool> canExecute)
        {
            this._execute = execute;
            this._canExecute = canExecute;
        }

        public void Execute(object parameter)
        {
            this._execute();
        }

        public bool CanExecute(object parameter)
        {
            return this._canExecute();
        }

        public event EventHandler CanExecuteChanged;
        public void NotifyCanExcuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }
}

 

 

3.2. CounterWPF.exe

では本体アプリ側を修正します。
先ほど作成した Counter.dll を参照設定しておくのを忘れずに。

 

3.2.1. MainWindow.xaml.cs

MainWindow.xaml.cs です。こちら、ほとんど空っぽになりました。

[MainWindow.xaml.cs]

using System.Windows;

namespace CounterWPF
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Click_Quit(object sender, System.Windows.RoutedEventArgs e)
        {
            Close();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (MessageBox.Show("プログラムを終了してよろしいですか?", "終了確認",
                MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                // プログラムを終了する
            }
            else
            {
                // プログラムを終了しない
                e.Cancel = true;
            }
        }
    }
}

 

 

3.2.2. Window の DataContext に Counter を新規登録

Window の DataContext へ Counter を新規登録します。
Window を選択後、DataContext の右側にある「新規作成」ボタンをクリックしてください。

Visual Studio 画面(1)

Visual Studio 画面(2)

 

「オブジェクトの選択」画面を表示するので、ここから CounterDll から class Counter を選択後、「OK」ボタンをクリックします。

Visual Studio 画面(3)

 

これで Window の DataContext へ Counter を登録することができました。

この時点の MainWindow.xaml.cs は以下の通りです。 

 

<Window
    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" mc:Ignorable="d" 
    xmlns:CounterDll="clr-namespace:CounterDll;assembly=CounterDll" x:Class="CounterWPF.Window1"
    Title="Counter on the WPF" Height="237.333" Width="347.467" ResizeMode="NoResize"
    Background="White" Closing="Window_Closing">

    <Window.DataContext>
        <CounterDll:Counter/>
    </Window.DataContext>
    <Grid>
        <Button x:Name="Button_Quit" HorizontalAlignment="Right" Margin="8,0,8,8" Width="100" 
            Content="Quit" VerticalAlignment="Bottom" Height="50" FontFamily="Arial" FontSize="16" 
            Click="Button_Click_Quit" ToolTip="プログラムを終了します。" />

        <TextBox x:Name="TextBox_CounterView" Margin="8,8,8,0" VerticalAlignment="Top" Height="59"
            TextWrapping="Wrap" FontFamily="Arial" FontSize="48" TextAlignment="Center" IsReadOnly="True"
            IsTabStop="False" ToolTip="値を表示します。">

            <TextBox.Background>
                <LinearGradientBrush EndPoint="0.496,0.022" StartPoint="0.498,0.936">
                    <GradientStop Color="#FF8392AD" Offset="0.004"/>
                    <GradientStop Color="#FFE2E5EA" Offset="1"/>
                </LinearGradientBrush>
            </TextBox.Background>
            <TextBox.Effect>
                <DropShadowEffect Direction="135" ShadowDepth="2"/>
            </TextBox.Effect>
        </TextBox>
        <Button x:Name="Button_Increment" HorizontalAlignment="Left" Margin="8,80,0,0" Width="100" 
            Content="+" FontFamily="Arial" FontSize="16" ToolTip="値を1つ加算します。" Height="50"
            VerticalAlignment="Top" />
        <Button x:Name="Button_Decrement" Margin="0,80,0,0" Width="100" Height="50"
            Content="-" FontFamily="Arial" FontSize="16" 
            ToolTip="値を1つ減算します。" HorizontalAlignment="Center" VerticalAlignment="Top"/>
        <Button x:Name="Button_Clear" Margin="0,80,8,0" Content="Clear" HorizontalAlignment="Right"
            Width="100" FontFamily="Arial" FontSize="16" ToolTip="値を0にクリアします。" Height="50"
            VerticalAlignment="Top" />
        <Rectangle Fill="White" Margin="8,14.6,8,0" VerticalAlignment="Top" Height="13.6" Opacity="0.85">
            <Rectangle.Effect>
                <BlurEffect Radius="10"/>
            </Rectangle.Effect>
        </Rectangle>
    </Grid>
</Window>

 

 

3.2.3. TextBox の Text を DataContext の Counter.CountValue へバインディング

TextBox の Text を DataContext の Counter.CountValue へデータ・バインディングします。

TextBox を選択後、Text の右側にある□をクリック、「データ・バインディング の作成...」を選びます。

Visual Studio 画面(4)

Visual Studio 画面(5)

 

「TextBox_CounterView.Text のデータ バインディングを作成」の画面を表示するので、パスから「CounterValue」を選択して「OK」ボタンを押します。

Visual Studio 画面(6)

 

下図の画面になったら成功です。TextBox の Text が CountValue へ結びついて "0" を表示できるようになりました。

Visual Studio 画面(7)

 

 

3.2.4. 「+」ボタンのコマンドを Counter.IncrementCommand へバインディング

「+」ボタンのコマンドを Counter.IncrementCommand へバインディングします。

「+」ボタンを選択後、Command の右側にある□をクリック、

Visual Studio 画面(8)

 

Visual Studio 画面(9)

Visual Studio 画面(10)

 

パスから「IncrementCommand」を選択し、「OK」をクリックします。

Visual Studio 画面(11)

 

3.2.5. 「ー」、「Clear」ボタンのコマンドをバインディング

3.2.4. と同様の手順で「ー」、「Clear」ボタンをバインディングします。

この時点の MainWindow.xaml.cs は以下の通りです。 

<Window
    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" mc:Ignorable="d" 
    xmlns:CounterDll="clr-namespace:CounterDll;assembly=CounterDll" x:Class="CounterWPF.Window1"
    Title="Counter on the WPF" Height="237.333" Width="347.467" 
    ResizeMode="NoResize" Background="White" Closing="Window_Closing">
    <Window.DataContext>
        <CounterDll:Counter/>
    </Window.DataContext>
    <Grid>
        <Button x:Name="Button_Quit" HorizontalAlignment="Right" Margin="8,0,8,8" Width="100" Content="Quit"
            VerticalAlignment="Bottom" Height="50" FontFamily="Arial" FontSize="16" Click="Button_Click_Quit"
            ToolTip="プログラムを終了します。" />
        <TextBox x:Name="TextBox_CounterView" Margin="8,8,8,0" VerticalAlignment="Top" Height="59"
            TextWrapping="Wrap" FontFamily="Arial" FontSize="48" TextAlignment="Center" IsReadOnly="True"
            IsTabStop="False" ToolTip="値を表示します。" Text="{Binding CountValue}">
            <TextBox.Background>
                <LinearGradientBrush EndPoint="0.496,0.022" StartPoint="0.498,0.936">
                    <GradientStop Color="#FF8392AD" Offset="0.004"/>
                    <GradientStop Color="#FFE2E5EA" Offset="1"/>
                </LinearGradientBrush>
            </TextBox.Background>
            <TextBox.Effect>
                <DropShadowEffect Direction="135" ShadowDepth="2"/>
            </TextBox.Effect>
        </TextBox>
        <Button x:Name="Button_Increment" HorizontalAlignment="Left" Margin="8,80,0,0" Width="100" Content="+" 
            FontFamily="Arial" FontSize="16" ToolTip="値を1つ加算します。" Height="50" VerticalAlignment="Top"
            Command="{Binding IncrementCommand, Mode=OneWay}" />
        <Button x:Name="Button_Decrement" HorizontalAlignment="Center" Margin="0,80,0,0" Content="-" Width="100"
            FontFamily="Arial" FontSize="16" ToolTip="値を1つ減算します。" VerticalAlignment="Top" Height="50"
            Command="{Binding DecrementCommand, Mode=OneWay}" />
        <Button x:Name="Button_Clear" Margin="0,80,8,0" Content="Clear" HorizontalAlignment="Right" Width="100"
            FontFamily="Arial" FontSize="16" ToolTip="値を0にクリアします。" Height="50" VerticalAlignment="Top"
            Command="{Binding ClearCommand, Mode=OneWay}" />
        <Rectangle Fill="White" Margin="8,14.6,8,0" VerticalAlignment="Top" Height="13.6" Opacity="0.85">
            <Rectangle.Effect>
                <BlurEffect Radius="10"/>
            </Rectangle.Effect>
        </Rectangle>
        
    </Grid>
</Window>

 

以上で完成です。

完成したプログラム画面

 

 

ライセンス

本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。

The MIT License (MIT)

Copyright © 2012-2022 Kinoshita Hidetoshi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

 

参考

 


 

変更履歴

2022-08-10 - ページデザイン更新
2016-05-03 - 更新
2012-05-16 - 新規作成

 

Programming Items トップページ

プライバシーポリシー