Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[WPF/xaml] xaml+C#で当番決めのためのルーレットを作る

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

Storyboard関連
- [WPF/xaml] xaml+C#で当番決めのためのルーレットを作る
- [WPF/xaml]Storyboardでアニメーションをつくる
- [WPF/xaml]Storyboardでアニメーションをつくる2(TargetPropertyの階層的な指定)

やりたいこと

週一回のミーティングの司会役を決めるためにネット上にあるルーレットページ?を使っているが、なんとなくxamlでもできそうな気がしたので試しに作ってみたい。

イメージはシンプルにこんな感じ。
image.png

やり方

下記のようにしてやる。
キーワードは「StoryBoard」。

  • [xaml]ルーレットの円を作成。
  • [C#]人数分の名前を、stringのListで持つ(以降、人数はListの件数で判断)
  • [C#]人数分の名前を、円の中に書く。
  • [C#]人数分の区切りの線を引く。
  • [C#]名前と区切り線を、一人分の角度*List番号分回転させて表示する。
  • [xaml]に、ルーレット(円の中身全体)を永遠に回し続けるStoryBoardを作成する。
  • [C#]ボタンをおしたら、ルーレットを回すStoryBoardをスタートする。

※一人分の角度は360/人数

コード

xaml

MainWindow.xaml
<Window x:Class="WpfApp38.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:WpfApp38"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="600"
        Loaded="Window_Loaded"
        Name="Root">

    <Window.Resources>
        <!-- ルーレットのアニメーション定義 -->
        <Storyboard  x:Key="StartRoulettea">
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"  Storyboard.TargetName="RouletteMain" Storyboard.TargetProperty="(Grid.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"
                                           RepeatBehavior="Forever" >
                <LinearDoubleKeyFrame KeyTime="00:00:00.0" Value="0" />
                <LinearDoubleKeyFrame KeyTime="00:00:00.5" Value="360" />
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>

    <Grid>
        <Viewbox Margin="20">
            <Grid Name="RouletteMain" RenderTransformOrigin="0.5,0.5">
                <Grid.RenderTransform>
                    <TransformGroup>
                        <!-- 回転の角度 -->
                        <RotateTransform Angle="0"/>
                    </TransformGroup>
                </Grid.RenderTransform>

                <!-- ルーレットの外側の円 -->
                <Ellipse Name="RouletteEllipse" Stroke="Black" StrokeThickness="5" Width="500" Height="500" >
                    <Ellipse.Fill>    
                        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">    
                            <GradientStop Color="Red" Offset="0.0" />    
                            <GradientStop Color="Orange" Offset="0.2" />    
                            <GradientStop Color="Yellow" Offset="0.4" />    
                            <GradientStop Color="LimeGreen" Offset="0.6" />    
                            <GradientStop Color="Blue" Offset="0.8" />    
                            <GradientStop Color="Violet" Offset="1.0" />    
                        </LinearGradientBrush>    
                    </Ellipse.Fill>
                </Ellipse>
            </Grid>

        </Viewbox>

        <!-- 上の線 -->
        <Rectangle Stroke="Black" StrokeThickness="10" Height="50" Width="5" VerticalAlignment="Top"/>

        <!-- スタートボタン -->
        <Button Name="StartButton" Width="100" Height="50" Content="スタート" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10" Click="Button_Click"/>
    </Grid>
</Window>

コードビハインド(cs)

MainWindow.xaml.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace WpfApp38
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// ルーレット回転中かどうか
        /// </summary>
        bool IsRounding = false;

        /// <summary>
        /// メンバー一覧
        /// ここにメンバー名をAddしたら、
        /// Window_Loaded()の中で枠を自動でつくる
        /// </summary>
        List<string> Members = new List<string>();

        /// <summary>
        /// コンストラクタ
        /// ここでメンバー登録をする
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            Members.Add("Aさん");
            Members.Add("Bさん");
            Members.Add("Cさん");
            Members.Add("Dさん");
            Members.Add("Eさん");
            Members.Add("Fさん");
            Members.Add("Gさん");
            Members.Add("Hさん");
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // 一人あたりの使用する角度を決める
            int anglePerOne = 360 / Members.Count;

            // 人数分の線を引き、名前のテキストを作成する
            for (int i = 0; i < Members.Count; i++)
            {
                ////////////////////////
                // 人数分の区切り線を引く
                ////////////////////////
                var tfgLine = new TransformGroup();
                tfgLine.Children.Add(new RotateTransform(i * anglePerOne));

                var line = new Line()
                {
                    X1 = 0,
                    Y1 = 0,
                    X2 = 0,
                    Y2 = RouletteEllipse.Width / 2,
                    StrokeThickness = 5,
                    Stroke = Brushes.Red,
                    Fill = Brushes.Transparent,
                    VerticalAlignment = VerticalAlignment.Top,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    RenderTransformOrigin = new Point(0, 1.0),
                    RenderTransform = tfgLine
                };

                RouletteMain.Children.Add(line);

                ////////////////////////
                // 人数分の名前を書く
                ////////////////////////
                int textHeight = 30;
                var tfgText = new TransformGroup();
                tfgText.Children.Add(new RotateTransform(-90 + (anglePerOne / 2) + (i * anglePerOne)));

                var text = new TextBlock()
                {
                    Text = Members[i],
                    Width = RouletteEllipse.Width / 2,                  // ルーレットの円の半分
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Right,
                    TextAlignment = TextAlignment.Center,
                    FontSize = textHeight,
                    RenderTransformOrigin = new Point(0, 0.5),
                    RenderTransform = tfgText
                };

                RouletteMain.Children.Add(text);
            }
        }

        /// <summary>
        /// ルーレットのスタート/ストップ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // ルーレットを回すためのStoryBoardを検索
            var sb = FindResource("StartRoulettea") as Storyboard;

            if (IsRounding == false)
            {
                // 回転開始(スタート)
                sb.Begin();

                StartButton.Content = "ストップ";
            }
            else
            {
                // 回転停止(ストップ)
                sb.Pause();

                StartButton.Content = "スタート";
            }

            IsRounding = !IsRounding;
        }
    }
}

できあがり

image.png

備考

背景のグラデーションあると見た目がさみしくないが、超目押し可能となるので問題。

バージョンアップ(200128)

  • 下記の機能を追加した。
    • ルーレット画像をクリップボードにコピーする
    • ルーレットスタート時、ドラムロール音を出す
    • ルーレットストップ時、シンバル音を出す
    • 音のONOFFをチェックボックスで切り替え可能
    • jsonファイルから、ルーレット上の名前を追加可能
    • 起動時、名前をランダムで並び替える
    • 当たった人を示す赤三角もまわる(目押し防止)

動くものはこちら

image.png

settings\NameList.json
{
    "Names" : [
        "1さん",
        "2さん",
        "3さん",
        "4さん",
        "5さん",
        "6さん",
        "7さん",
        "8さん",
        "9さん",
        "10さん",
        "11さん"
    ]
}

コード

https://github.com/tera1707/WPF-/tree/master/031_XamlRoulette

参考

++C++
https://ufcpp.net/study/dotnet/wpf_xamlani.html

自分のページ
[WPF/xaml]Storyboardでアニメーションをつくる
https://qiita.com/tera1707/items/a7fcdd95fc3120ae3c8b

tera1707
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away