もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f
Storyboard関連
- [WPF/xaml] xaml+C#で当番決めのためのルーレットを作る
- [WPF/xaml]Storyboardでアニメーションをつくる
- [WPF/xaml]Storyboardでアニメーションをつくる2(TargetPropertyの階層的な指定)
やりたいこと
週一回のミーティングの司会役を決めるためにネット上にあるルーレットページ?を使っているが、なんとなくxamlでもできそうな気がしたので試しに作ってみたい。
やり方
下記のようにしてやる。
キーワードは「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;
}
}
}
できあがり
備考
背景のグラデーションあると見た目がさみしくないが、超目押し可能となるので問題。
バージョンアップ(200128)
- 下記の機能を追加した。
- ルーレット画像をクリップボードにコピーする
- ルーレットスタート時、ドラムロール音を出す
- ルーレットストップ時、シンバル音を出す
- 音のONOFFをチェックボックスで切り替え可能
- jsonファイルから、ルーレット上の名前を追加可能
- 起動時、名前をランダムで並び替える
- 当たった人を示す赤三角もまわる(目押し防止)
settings\NameList.json
{
"Names" : [
"1さん",
"2さん",
"3さん",
"4さん",
"5さん",
"6さん",
"7さん",
"8さん",
"9さん",
"10さん",
"11さん"
]
}
コード
参考
++C++
https://ufcpp.net/study/dotnet/wpf_xamlani.html
自分のページ
[WPF/xaml]Storyboardでアニメーションをつくる
https://qiita.com/tera1707/items/a7fcdd95fc3120ae3c8b