WPFでパイプ流体の流れをシミュレートする-パスのアニメーションWPFでパイプ流体の流れをシミュレートする-パスアニメーション

WPFでパイプ流体の流れをシミュレートする-パスのアニメーションWPFでパイプ流体の流れをシミュレートする-パスアニメーション

WPFの大きな特徴はアニメーションシステムであり、アニメーションを使用してWin Formでは実現が困難な多くの効果を達成することができる。

最后更新 2023/01/15 12:46
ludewig
预计阅读 10 分钟
分类
WPF
专题
WPF UIデザイン
标签
.NET C# WPF Winform

WPFの大きな特徴はアニメーションシステムであり、アニメーションを使用してWin Formでは実現が困難な多くの効果を達成することができる。最近、インターネットで神様がWPFアニメーションを使って特定の経路に沿ってオブジェクトを前後に動かす効果を実現しているのを見て、自分で試してみたいと思います。

1. 単純パスのアニメーションたんじゅんぱすのあにめーしょん

最も単純なパスアニメーションから始めましょう。ブロックに線分を加えて、ブロックを線分の始点から線分の終点に移動させます。フロントページのコードは以下の通りです。

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="80"></RowDefinition>
    <RowDefinition></RowDefinition>
  </Grid.RowDefinitions>
  <WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center">
    <button x:Name="btnAnimo" Click="btnAnimo_Click" Margin="0,0,10,0">
      开始
    </button>
  </WrapPanel>
  <Grid Grid.Row="1">
    <canvas x:Name="cvsMain">
      <Path
        x:Name="path1"
        Data="M100,100 L300,100 400,200 500,200"
        Stroke="LightGreen"
        StrokeThickness="20"
        StrokeLineJoin="Round"
      ></Path>
    </canvas>
  </Grid>
</Grid>

バックグラウンド·ロジック·コードは以下の通り。

private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
    AnimationByPath(cvsMain, path1,path1.StrokeThickness);
}

/// <summary>
/// 路径动画
/// </summary>
/// <param name="cvs">画板</param>
/// <param name="path">路径</param>
/// <param name="target">动画对象</param>
/// <param name="duration">时间</param>
private void AnimationByPath(Canvas cvs, Path path,double targetWidth, int duration = 5)
{
    #region 创建动画对象
    Rectangle target = new Rectangle();
    target.Width = targetWidth;
    target.Height = targetWidth;
    target.Fill = new SolidColorBrush(Colors.Orange);
    cvs.Children.Add(target);
    Canvas.SetLeft(target, -targetWidth / 2);
    Canvas.SetTop(target, -targetWidth / 2);
    target.RenderTransformOrigin = new Point(0.5, 0.5);
    #endregion

    MatrixTransform matrix = new MatrixTransform();
    TransformGroup groups = new TransformGroup();
    groups.Children.Add(matrix);
    target.RenderTransform = groups;
    string registname = "matrix" + Guid.NewGuid().ToString().Replace("-", "");
    this.RegisterName(registname, matrix);
    MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath();
    matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(path.Data.ToString()));
    matrixAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
    matrixAnimation.DoesRotateWithTangent = true;//跟随路径旋转
    matrixAnimation.RepeatBehavior = RepeatBehavior.Forever;//循环
    Storyboard story = new Storyboard();
    story.Children.Add(matrixAnimation);
    Storyboard.SetTargetName(matrixAnimation, registname);
    Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty));

    story.FillBehavior = FillBehavior.Stop;
    story.Begin(target, true);
}

其中的关键点在于动态创建一个Rectangle正方体作为动画对象,正方体的宽高设为跟路径宽度一致,并设置正方体的变换原点为中心点(RenderTransformOrigin ="0.5,0.5"),确保正方体随路径移动时也能随着路径旋转。最终效果如下:

2. 逆パスのアニメーション逆パスあにめーしょん

前の例に基づいて、線分を複数の連続線分に変更しても、円弧を追加しても効果はなく、小さな正方形はパスに沿って移動します。パスには始点と終点があり、通常の状況ではアニメーションオブジェクトは始点から終点に移動しますが、オブジェクトを終点から始点に移動させることはできますか?

それは別の考え方であり、元のパスを反転させ、始点と終点を反転させ、元のパスと同じ外観がデータとは逆のパスを得ることができませんか?アニメーションオブジェクトを反転させたパスに沿って移動させると、視覚的には終点から始点に移動します。

この問題を解決する鍵は、経路データの変換です。

private string ConvertPathData(string data)
{
    data = data.Replace("M", "");
    Regex regex = new Regex("[a-z]", RegexOptions.IgnoreCase);
    MatchCollection mc = regex.Matches(data);
    //item1 从上一个位置到当前位置开始的字符 (match.Index=原始字符串中发现捕获的子字符串的第一个字符的位置。)
    //item2 当前发现的匹配符号(L C Z M)
    List<Tuple<string, string>> tmps = new List<Tuple<string, string>>();
    int index = 0;
    for (int i = 0; i < mc.Count; i++)
    {
        Match match = mc[i];
        if (match.Index != index)
        {
            string str = data.Substring(index, match.Index - index);
            tmps.Add(new Tuple<string, string>(str, match.Value));
        }
        index = match.Index + match.Length;
        if (i + 1 == mc.Count)//last
        {
            tmps.Add(new Tuple<string, string>(data.Substring(index), match.Value));
        }
    }
    List<string[]> arrys = new List<string[]>();
    Regex regexnum = new Regex(@"(\-?\d+\.?\d*)", RegexOptions.IgnoreCase);
    for (int i = 0; i < tmps.Count; i++)
    {
        MatchCollection childMcs = regexnum.Matches(tmps[i].Item1);
        if (childMcs.Count % 2 != 0)
        {
            continue;
        }
        int groups = childMcs.Count / 2;
        var strTmp = new string[groups];
        for (int j = 0; j < groups; j++)
        {
            string cdatas = childMcs[j * 2] + "," + childMcs[j * 2 + 1];//重组数据
            strTmp[j] = cdatas;
        }
        arrys.Add(strTmp);
    }

    List<string> result = new List<string>();
    for (int i = arrys.Count - 1; i >= 0; i--)
    {
        string[] clist = arrys[i];
        for (int j = clist.Length - 1; j >= 0; j--)
        {
            if (j == clist.Length - 2 && i > 0)//对于第二个元素增加 L或者C的标识
            {
                var pointWord = tmps[i - 1].Item2;//获取标识
                result.Add(pointWord + clist[j]);
            }
            else
            {
                result.Add(clist[j]);
                if (clist.Length == 1 && i > 0)//说明只有一个元素 ex L44.679973,69.679973
                {
                    result.Add(tmps[i - 1].Item2);
                }
            }
        }
    }
    return "M" + string.Join(" ", result);

}

また、アニメーションオブジェクトとしての正方形は、任意のコントロールオブジェクトに置き換えることができ、イメージポイントのために、正方形を矢印に置き換えます。同時に、前方アニメーションと逆アニメーションを区別するために、パスも異なる色に設定されます。修正後のコードは以下の通り。

/// <summary>
/// 正向
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
    AnimationByPath(cvsMain, path1,path1.StrokeThickness,false,3);
}
/// <summary>
/// 反向
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReback_Click(object sender, RoutedEventArgs e)
{
    AnimationByPath(cvsMain, path1, path1.StrokeThickness, true, 3);
}



/// <summary>
/// 路径动画
/// </summary>
/// <param name="cvs">画板</param>
/// <param name="path">路径</param>
/// <param name="targetWidth">动画对象宽高</param>
/// <param name="isInverse">是否反向</param>
/// <param name="duration">动画时间</param>
private void AnimationByPath(Canvas cvs, Path path, double targetWidth, bool isInverse = false, int duration = 5)
{
    Polygon target = new Polygon();
    target.Points = new PointCollection()
    {
        new Point(0,0),
        new Point(targetWidth/2,0),
        new Point(targetWidth,targetWidth/2),
        new Point(targetWidth/2,targetWidth),
        new Point(0,targetWidth),
        new Point(targetWidth/2,targetWidth/2)
    };

    if (isInverse)//反向
    {
        target.Fill = new SolidColorBrush(Colors.DeepSkyBlue);
    }
    else//正向
    {
        target.Fill = new SolidColorBrush(Colors.Orange);
    }

    cvs.Children.Add(target);
    Canvas.SetLeft(target, -targetWidth / 2);
    Canvas.SetTop(target, -targetWidth / 2);
    target.RenderTransformOrigin = new Point(0.5, 0.5);

    MatrixTransform matrix = new MatrixTransform();
    TransformGroup groups = new TransformGroup();
    groups.Children.Add(matrix);
    target.RenderTransform = groups;
    string registname = "matrix" + Guid.NewGuid().ToString().Replace("-", "");
    this.RegisterName(registname, matrix);
    MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath();
    if (!isInverse)//正向
    {
        matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(path.Data.ToString()));
    }
    else//反向
    {
        string data = ConvertPathData(path.Data.ToString());
        matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(data));
    }
    matrixAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
    matrixAnimation.DoesRotateWithTangent = true;//旋转
    matrixAnimation.RepeatBehavior = RepeatBehavior.Forever;
    Storyboard story = new Storyboard();
    story.Children.Add(matrixAnimation);
    Storyboard.SetTargetName(matrixAnimation, registname);
    Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty));

    story.FillBehavior = FillBehavior.Stop;
    story.Begin(target, true);
}

効果就变成下的样子了,是不是有点意思了?

3. パイプ流体のシミュレーションのアニメーションぱいぷりゅうたいのしみゅれーしょんのあにめーしょん

上記の基礎を念頭に置いて、水道管の水の流れをシミュレートするアニメーションを改善することを検討してください。もちろん、パイプは1つ以上、複数、直径が異なります。ポンプを追加すると、ポンプが水を流れ始め、ポンプが反転し、水が逆流します。最も重要な問題は前のステップで解決されたので、アニメーションオブジェクトの回転を制御するためのキーフレームアニメーションを追加します。

フォアグラウンドコードは:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="80"></RowDefinition>
    <RowDefinition></RowDefinition>
  </Grid.RowDefinitions>
  <WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center">
    <button x:Name="btnAnimo" Click="btnAnimo_Click" Margin="0,0,10,0">
      正转
    </button>
    <button x:Name="btnReback" Click="btnReback_Click" Margin="0,0,10,0">
      反转
    </button>
  </WrapPanel>
  <Grid Grid.Row="1">
    <canvas x:Name="cvsMain">
      <Path
        x:Name="path1"
        Data="M100,100 L300,100 300,200 400,200"
        Stroke="LightGreen"
        StrokeThickness="20"
        StrokeLineJoin="Round"
      ></Path>
      <Path
        x:Name="path2"
        Data="M200,300 L350,300 350,200"
        Stroke="LightGreen"
        StrokeThickness="12"
        StrokeLineJoin="Round"
      ></Path>
      <Path
        x:Name="path3"
        Data="M450,223 L550,223 650,100 750,100 800,150"
        Stroke="LightGreen"
        StrokeThickness="16"
        StrokeLineJoin="Round"
      ></Path>
      <image
        Source="fan.png"
        Width="50"
        Height="50"
        Canvas.Left="400"
        Canvas.Top="185"
      ></image>
      <image
        x:Name="imgFan"
        Source="fan-inner.png"
        Width="24"
        Height="24"
        Canvas.Left="410"
        Canvas.Top="197"
        RenderTransformOrigin="0.5,0.5"
      ></image>
    </canvas>
  </Grid>
</Grid>

バックグラウンドコードを以下に変更。

/// <summary>
/// 正转
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
    // 原文第三个参数传的是this.path[x].Width,其实应该是this.path[x].StrokeThickness
    AnimationByPath(this.cvsMain, this.path1, this.path1.StrokeThickness,false, 3);
    AnimationByPath(this.cvsMain, this.path2, this.path2.StrokeThickness,false, 3);
    AnimationByPath(this.cvsMain, this.path3, this.path3.StrokeThickness,false, 3);

    StoryByOrient(this.imgFan,0, 3);
}
/// <summary>
/// 反转
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReback_Click(object sender, RoutedEventArgs e)
{
    // 原文第三个参数传的是this.path[x].Width,其实应该是this.path[x].StrokeThickness
    AnimationByPath(this.cvsMain, this.path1, this.path1.StrokeThickness, true, 3);
    AnimationByPath(this.cvsMain, this.path2, this.path2.StrokeThickness, true, 3);
    AnimationByPath(this.cvsMain, this.path3, this.path3.StrokeThickness, true, 3);

    StoryByOrient(this.imgFan, 1, 3);
}

/// <summary>
/// 旋转动画
/// </summary>
/// <param name="img">动画对象</param>
/// <param name="orientation">顺时针/逆时针</param>
/// <param name="duration"></param>
private void StoryByOrient(Image img, int orientation, int duration = 5)
{
    Storyboard storyboard = new Storyboard();//创建故事板
    DoubleAnimation doubleAnimation = new DoubleAnimation();//实例化一个Double类型的动画
    RotateTransform rotate = new RotateTransform();//旋转转换实例
    img.RenderTransform = rotate;//给图片空间一个转换的实例
    storyboard.RepeatBehavior = RepeatBehavior.Forever;//设置重复为 一直重复
    storyboard.SpeedRatio = 2;//播放的数度
    //设置从0 旋转360度
    doubleAnimation.From = 0;
    if (orientation==0)//顺时针
    {
        doubleAnimation.To = 360;
    }
    else//逆时针
    {
        doubleAnimation.To = -360;
    }
    doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));//播放时间长度为2秒
    Storyboard.SetTarget(doubleAnimation, img);//给动画指定对象
    Storyboard.SetTargetProperty(doubleAnimation,
new PropertyPath("RenderTransform.Angle"));//给动画指定依赖的属性
    storyboard.Children.Add(doubleAnimation);//将动画添加到动画板中
    storyboard.Begin(img);//启动动画
}

最終的な効果を見てください:

それはかなりのものです。

注:三番目のケースコードは画像が欠けています。ウェブマスターは原文のGIF図によって部分を傍受し、パラメータを設定して、上の図の効果を実行できます:https//github.com/dotnet9/TerminalMACS.ManagerForWPF/tree/master/src/Demo/PathAnimationDemo

プログラミングは楽しく、あきらめないでください。

この記事を再現。

アーティスト:Ludewig

WPFエッセイ(9)-経路アニメーションを用いたパイプライン流体の流れのシミュレーション

原文へのリンク:https//blog.csdn.net/lordwish/article/details/85007867

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 2025/09/13

WPFからAvaloniaへの移行シリーズ:WPFプログラムをAvaloniaに移行する必要がある理由

ここ数年、当社のホストソフトウェアは主にWPFとWin Formで開発されてきました。これらのテクノロジーはWindowsプラットフォームで非常にうまく機能し、小規模なパイロット生産から今日の大規模なデリバリまでの段階を経てきました。しかし、ビジネスの成長と顧客のニーズの変化に伴い、単一のWindowsテクノロジースタックは私たちが乗り越えなければならないハードルになりました。

继续阅读