1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WPFでズンドコキヨシ&テッテーテレッテーしてみた

Last updated at Posted at 2017-06-01

皆様ご存じズンドコキヨシをWPFでやってみた
ついでに最近ずっと聞き続けているGYARIさんのテッテーテレッテーも同じようにやってみた('ω')

18/07/17 MVVMらしく改修

作ったもの

ZundokoSample.gif

入力パターン(カンマ区切り)、完成パターン(カンマ区切り)と出力文字を指定してStartを押すと入力パターンからランダムに文字を出力して、完成パターンと一致したら出力文字を末尾につけてダイアログを出して終了するよ(=゚ω゚)ノ
また、文字出力中にStopを押すと停止できる(ΦωΦ)

作り方

ライブラリは
Prism
ReactiveProperty
MahApps.Metro
を使用

Model

Modelといっても、今回は入力パターンと完成パターンを指定して完成パターンに一致するまでランダムに文字列を出力するメソッドを作るだけ

StreamCreater.cs
public static class StreamCreater
{
    static readonly private Queue<string> queue = new Queue<string>();
    static private Random random = new Random();
    /// <summary>
    /// 入力したパターンからランダムに出力し、完成パターンと一致すれば出力を終了する
    /// </summary>
    /// <param name="inputPattern">入力パターン</param>
    /// <param name="finishedPattern">完成パターン</param>
    /// <returns></returns>
    public static IEnumerable<string> Create(string[] inputPattern, string[] finishedPattern)
    {
        queue.Clear();
        while (!queue.SequenceEqual(finishedPattern))
        {
            queue.Enqueue(inputPattern[random.Next(inputPattern.Count())]);
            if (queue.Count == finishedPattern.Count() + 1)
            {
                queue.Dequeue();
            }
            yield return queue.Last();
        }
    }
}

戻り値の型をIEnumerableにしてyield returnすることでforeachで一個づつ値が取れるようにしている

ViewModel

ShellViewModel.cs
public class ShellViewModel : BindableBase
{
    public ReactiveProperty<string> LeftInputPattern { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<string> LeftFinishedPattern { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<string> LeftOutput { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<bool> IsLeftChecked { get; private set; } = new ReactiveProperty<bool>(true);

    public ReactiveProperty<string> RightInputPattern { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<string> RightFinishedPattern { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<string> RightOutput { get; private set; } = new ReactiveProperty<string>("");
    public ReactiveProperty<bool> IsRightChecked { get; private set; } = new ReactiveProperty<bool>(false);

    public ReactiveProperty<bool> IsStarted { get; private set; } = new ReactiveProperty<bool>(false);
    public ReactiveProperty<int> StreamCount { get; private set; } = new ReactiveProperty<int>();
    public ReactiveProperty<string> ResultText { get; private set; } = new ReactiveProperty<string>();
    public ReactiveProperty<string> StartStopButtonText { get; private set; } = new ReactiveProperty<string>("Start");

    public InteractionRequest<Notification> Notification { get; } = new InteractionRequest<Notification>();

    public ReactiveCommand StreamStartCommand { get; private set; } = new ReactiveCommand();

    private CancellationTokenSource cancellationTokenSource;

    public ShellViewModel()
    {
        StreamStartCommand.Subscribe(async () =>
        {
            if (!this.IsStarted.Value)
            {
                if (this.IsLeftChecked.Value) await this.StreamStart(this.LeftInputPattern.Value, this.LeftFinishedPattern.Value, this.LeftOutput.Value);
                else if (this.IsRightChecked.Value) await this.StreamStart(this.RightInputPattern.Value, this.RightFinishedPattern.Value, this.RightOutput.Value);
            }
            else this.StreamStop();
        });
    }

    /// <summary>
    /// Streamを開始する
    /// </summary>
    /// <param name="input"></param>
    /// <param name="finished"></param>
    /// <param name="output"></param>
    private async Task StreamStart(string input, string finished, string output)
    {
        if (this.IsStarted.Value) return;
        if (string.IsNullOrWhiteSpace(input) || string.IsNullOrWhiteSpace(finished)) return;

        var inputPattern = input.Split(',');
        var finishedPattern = finished.Split(',');
        //パターンが一致可能か確認
        if (finishedPattern.Except(inputPattern).Any())
        {
            this.Notification.RaiseEx("エラー", "完成パターン内に、入力パターンに存在しないものがあります。");
            return;
        }
        //初期化
        this.StartStopButtonText.Value = "Stop";
        this.IsStarted.Value = true;
        this.StreamCount.Value = 0;
        this.ResultText.Value = "";
        this.cancellationTokenSource = new CancellationTokenSource();

        //Streamを非同期で生成
        var res = await Task.Run(async () =>
        {
            foreach (var item in StreamCreater.Create(inputPattern, finishedPattern))
            {
                if (cancellationTokenSource.IsCancellationRequested) return false;
                this.ResultText.Value += item + ',';
                this.StreamCount.Value++;
                await Task.Delay(1);
            }
            this.ResultText.Value += output;
            this.IsStarted.Value = false;
            return true;
        }, this.cancellationTokenSource.Token);
        //キャンセルされなければダイアログ表示
        if (res)
        {
            this.Notification.RaiseEx("パターン一致", $"{finished},{output}\r\n{this.StreamCount.Value}回で完成パターンと一致しました。");
            this.StartStopButtonText.Value = "Start";
        }
    }
    /// <summary>
    /// 動作中のStreamを停止する
    /// </summary>
    private void StreamStop()
    {
        if (!this.IsStarted.Value) return;
        this.StartStopButtonText.Value = "Start";
        this.IsStarted.Value = false;
        this.cancellationTokenSource.Cancel();
    }
}

ここではViewから入力パターンなどを受け取ったり、結果を表示したりするプロパティやコマンド、ダイアログ表示の為のInteractionRequest等を定義している
ここのコマンドで上記のStreamCreater.Create()している
また、ViewModelはINotifyPropertyChangedを実装していないとメモリリークするようなのでINotifyPropertyChangedを実装したPrismのBindableBaseを継承している
コードが少し汚いけどお許しを…
今回はViewModelに実行処理も書いているが、この処理もModel層に記述して呼び出しだけ行ったほうがよりMVVMらしいと思う

View

xml:Shell.xaml
<metro:MetroWindow x:Class="ZunDokoKIYOSHI.Views.Shell"
        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:prism="http://prismlibrary.com/"
        xmlns:prismEx="clr-namespace:Prism.InteractivityExtension;assembly=Prism.InteractivityExtension"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls"
        xmlns:vm="clr-namespace:ZunDokoKIYOSHI.ViewModels"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        d:DataContext="{d:DesignInstance {x:Type vm:ShellViewModel}}"
        xmlns:local="clr-namespace:ZunDokoKIYOSHI.Views"
        mc:Ignorable="d"
        Title="Shell" Height="450" Width="800">
    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding Notification}">
            <prismEx:PopupMetroWindowAction IsModal="True"/>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <StackPanel
                Orientation="Vertical">
                <RadioButton
                    HorizontalAlignment="Center"
                    GroupName="StreamSelect"
                    IsChecked="{Binding IsLeftChecked.Value}"/>
                <TextBox
                    Text="{Binding LeftInputPattern.Value}"
                    metro:TextBoxHelper.Watermark="入力パターン"/>
                <TextBox
                    Text="{Binding LeftFinishedPattern.Value}"
                    metro:TextBoxHelper.Watermark="完成パターン"/>
                <TextBox
                    Text="{Binding LeftOutput.Value}"
                    metro:TextBoxHelper.Watermark="出力文字"/>
            </StackPanel>
            <StackPanel
                Orientation="Vertical"
                Grid.Column="1">
                <RadioButton
                    HorizontalAlignment="Center"
                    GroupName="StreamSelect"
                    IsChecked="{Binding IsRightChecked.Value}"/>
                <TextBox
                    Text="{Binding RightInputPattern.Value}"
                    metro:TextBoxHelper.Watermark="入力パターン"/>
                <TextBox
                    Text="{Binding RightFinishedPattern.Value}"
                    metro:TextBoxHelper.Watermark="完成パターン"/>
                <TextBox
                    Text="{Binding RightOutput.Value}"
                    metro:TextBoxHelper.Watermark="出力文字"/>
            </StackPanel>
        </Grid>
        <Button
            Grid.Row="1"
            Content="{Binding StartStopButtonText.Value}"
            Command="{Binding StreamStartCommand}"/>
        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel
                Orientation="Horizontal">
                <TextBlock Text="Count:"/>
                <TextBlock Text="{Binding StreamCount.Value}"/>
            </StackPanel>
            <TextBox
                Grid.Row="1"
                IsReadOnly="True"
                TextWrapping="Wrap"
                Text="{Binding ResultText.Value}"
                ScrollViewer.VerticalScrollBarVisibility="Auto"/>
        </Grid>
    </Grid>
</metro:MetroWindow>

MahApps.Metroを使用している為、WindowはMetroWindowにしている

    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding Notification}">
            <prismEx:PopupMetroWindowAction IsModal="True"/>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>

ここではダイアログを表示する為のInteractionRequestTriggerを定義している
Prismの標準ではMetroWindowのダイアログを出すことができない為、以前作ったPrism.InteractivityExtensionを使用している

まとめ

遊び半分で作った(゚∀゚)
無駄にDL先を置いておく
コードの全容はココ

1
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?