「読み込み中」とか「しばらくお待ちください」な時に表示される円がくるくるアニメーションしてるようなやつを作る。
↓こんなやつ。
XAML
<UserControl x:Class="WpfApplication1.WaitingCircle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfApplication1" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Viewbox Stretch="Uniform"> <Canvas x:Name="MainCanvas" Width="100" Height="100"> <Canvas.RenderTransform> <RotateTransform x:Name="MainTrans" CenterX="50" CenterY="50"/> </Canvas.RenderTransform> </Canvas> </Viewbox> </UserControl>
コントロールのサイズに合わせて子要素を拡大縮小する (9行目)
Canvasの中にくるくる回る円の要素を書き込んでいくが、座標が計算しやすいように100x100のサイズで作っていく。
但し、このWaitingCircleコントロールがどのようなサイズで配置されてもいいように、Viewboxコントロールを使ってサイズの調整を行う。
StretchプロパティにUniformを指定して、縦横比を保ちつつサイズいっぱいに引き伸ばす。
描画領域を作る (10行目)
座標が計算しやすいように100x100のサイズにしたCanvasコントロールを準備。
Canvasの中にくるくる回る円の要素を作っていくが、座標は計算で求める方が楽なのでコードの方で行う。
アニメーションの為の回転変換を用意する (11~13行目)
Canvasを回転させるアニメーションを作る為RotateTransformを用意する。
アニメーションの細かい設定はコードの方で行う。
コード
public partial class WaitingCircle : UserControl { public static readonly DependencyProperty CircleColorProperty = DependencyProperty.Register( "CircleColor", // プロパティ名を指定 typeof(Color), // プロパティの型を指定 typeof(WaitingCircle), // プロパティを所有する型を指定 new UIPropertyMetadata(Color.FromRgb(90, 117, 153), (d, e) => {(d as WaitingCircle).OnCircleColorPropertyChanged(e); })); public Color CircleColor { get { return (Color)GetValue(CircleColorProperty); } set { SetValue(CircleColorProperty, value); } } public WaitingCircle() { InitializeComponent(); double cx = 50.0; double cy = 50.0; double r = 45.0; int cnt = 14; double deg = 360.0 / (double)cnt; double degS = deg * 0.2; for (int i = 0; i < cnt; ++i) { var si1 = Math.Sin((270.0 - (double)i * deg) / 180.0 * Math.PI); var co1 = Math.Cos((270.0 - (double)i * deg) / 180.0 * Math.PI); var si2 = Math.Sin((270.0 - (double)(i + 1) * deg + degS) / 180.0 * Math.PI); var co2 = Math.Cos((270.0 - (double)(i + 1) * deg + degS) / 180.0 * Math.PI); var x1 = r * co1 + cx; var y1 = r * si1 + cy; var x2 = r * co2 + cx; var y2 = r * si2 + cy; var path = new Path(); path.Data = Geometry.Parse(string.Format("M {0},{1} A {2},{2} 0 0 0 {3},{4}", x1, y1, r, x2, y2)); path.Stroke = new SolidColorBrush(Color.FromArgb((byte)(255 - (i * 256 / cnt)), CircleColor.R, CircleColor.G, CircleColor.B)); path.StrokeThickness = 10.0; MainCanvas.Children.Add(path); } var kf = new DoubleAnimationUsingKeyFrames(); kf.RepeatBehavior = RepeatBehavior.Forever; for (int i = 0; i < cnt; ++i) { kf.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(i * 80)), Value = i * deg }); } MainTrans.BeginAnimation(RotateTransform.AngleProperty, kf); } public void OnCircleColorPropertyChanged(DependencyPropertyChangedEventArgs e) { if (null == MainCanvas) return; if (null == MainCanvas.Children) return; foreach (var child in MainCanvas.Children) { var shp = child as Shape; var sb = shp.Stroke as SolidColorBrush; var a = sb.Color.A; shp.Stroke = new SolidColorBrush(Color.FromArgb(a, CircleColor.R, CircleColor.G, CircleColor.B)); } } }
円の色を変えられるプロパティを追加 (3~14行目)
円の色を指定するCircleColorを依存関係プロパティとして作成。
依存関係プロパティについてはこちらを参照。
円を書く (21~43行目)
円弧の透明度を変えながら複数作成して円を作る。
cx,cy ・・・円の中心座標
r ・・・円の半径
cnt ・・・円の分割数(14分割)
deg ・・・分割した1つ分の角度
degS ・・・円弧と円弧の隙間の角度
円弧はPathクラスを使って作成する。
PathクラスのDataプロパティはパス マークアップ構文を使って作成。
マークアップ構文で円弧を書く場合は以下のようになる。
M [始点X],[始点Y] A [円半径X],[円半径Y] [回転角] [180度以上の時1] [正の角の時1] [終点X],[終点Y]
アニメーションを作成する (45~55行目)
Canvasを回転させるアニメーションを作る。
DiscreteDoubleKeyFrameを使うと補間せずに値が変わるので、フレーム毎の移動量を円弧の角度(deg)と合わせるといい感じになる。
RotateTransformのBeginAnimationメソッドを使って、Angleプロパティへのアニメーションを開始する。(55行目)
円の色を変えられた時の処理 (58~70行目)
色が変更されたらCanvas内の要素(円弧)のstrokeプロパティのブラシを再作成するが、アルファ値だけは元の値を使ってR,G,Bのみを変更する。
コメントをお書きください
来訪者 (金曜日, 12 1月 2018 18:54)
wpfで本機能を実現できました。
コードがシンプルでユーザコントロールでもあったのでとても使いやすく助かりました。
ありがとうございました。
2 (月曜日, 17 2月 2020 11:57)
助かりました。
来訪者2 (金曜日, 03 2月 2023 14:40)
13フレームから0フレームに移るときに1フレーム分とびませんか?
このままのコードで実行すると、1周後に一瞬かくつく感じがします。
0フレーム時の状態が一瞬しかないからだと思いますので、
Forの後に、14番目のフレームとして、Value=0のフレームを同じように登録するとスムーズに
回転するようになります。
kf.KeyFrames.Add(new DiscreteDoubleKeyFrame()
{
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cnt * 80)),
Value = 0
});