WPF水流动画(使用转换器模拟逻辑门控制水流信号)

前言

在使用WPF绘制流程图并模拟水流动画时,往往既需要控制阀泵的开合,又要控制动画启停。倘若能够将阀泵的开合与动画播放建立逻辑关系,这样就能够让业务代码“专心”地去控制阀泵开关,而不需要处理界面的展示。

动画示例

说明:动画中蚂蚁线的显示与隐藏是通过属性绑定配合转换器实现的。

动画解析

1、示例中【进水阀】前是一直有水流信号的,因此只要【进水阀】开,阀后即显示水流动画(此动画受一个属性控制,用单值转换器【IValueConverter】即可);

2、【进水阀】开启后,任意一个【提升泵】开启,即可出现汇入【储水池】的水流。为了与现场情况保持一致,示例中三个泵分别对应一根支管,连接到总管。水流从泵出发,通过支管流入总管,最终汇入【储水池】。

动画实现

1、水流路径处理

①当只有一个【提升泵】开启时,不用考虑路径重叠的问题,水流动画可以由一条折线来完成。

以下为单泵开启时各个泵的水流路径:

②当有多个泵开启时,只有最左边开启的泵需要绘制完整折线,其余启动泵只需绘制【支管】的水流路线。

如下,三泵全开状态。【1#泵】绘制从泵到池的完整路径,【2#泵】【3#泵】只绘制支管路径:

如下,只有【2#泵】【3#泵】开启。【2#泵】绘制从泵到池的完整路径,【3#泵】只绘制支管路径:

2、逻辑整理

①对于【1#泵】,以上两种情况下的动画路径是相同的。动画仅受【进水阀】和【1#泵】两个开关量的影响,可将其控制逻辑等效为【与门】。

②对于【2#泵】【3#泵】,【进水阀】和泵自身的开关信号是水流动画开启的必要条件,形成第一道【与门】。第二道门则根据实际情况决定:若左侧有任意泵开启,则仅绘制支管路径,此路径的第二道门为【或门】;若左侧泵全部关闭,则绘制泵到水池的完整路径,此路径的第二道门为【或非门】。

3、逻辑门实现

逻辑门借助多值转换器(IMultiValueConverter)来实现。

①与门

internal class AndGateToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Any(a => !(bool)a))
        {
            return Visibility.Collapsed;
        }
        return Visibility.Visible;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 ②或门

在属性绑定中,只能使用一个转换器,所以将【与门】的逻辑整合到【或门】中,需要进行第一道【与门】判定的参数索引通过ConverterParameter传入。

internal class OrGateToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // parameter传入的是参与第一道“与门”判定的索引,多个索引之间用逗号隔开
        List<int> andGateIndexes = parameter == null
            ? new List<int>()
            : parameter.ToString().Split(',').Select(a => System.Convert.ToInt32(a)).ToList();

        // 先进行“与门”判定
        if (andGateIndexes.Any(a => !(bool)values[a]))
        {
            return Visibility.Collapsed;
        }

        // 排除“与门”判定索引,进行“或门”判定
        for (int i = 0; i < values.Length; i++)
        {
            if (!andGateIndexes.Contains(i) && (bool)values[i])
            {
                return Visibility.Visible;
            }
        }
        return Visibility.Collapsed;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

③或非门

同样将第一道【与门】判定整合到此转换器中。

internal class NOrGateToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // parameter传入的是参与第一道“与门”判定的索引,多个索引之间用逗号隔开
        List<int> andGateIndexes = parameter == null
            ? new List<int>()
            : parameter.ToString().Split(',').Select(a => System.Convert.ToInt32(a)).ToList();

        // 先进行“与门”判定
        if (andGateIndexes.Any(a => !(bool)values[a]))
        {
            return Visibility.Collapsed;
        }

        // 排除“与门”判定索引,进行“或非门”判定
        for (int i = 0; i < values.Length; i++)
        {
            if (!andGateIndexes.Contains(i) && (bool)values[i])
            {
                return Visibility.Collapsed;
            }
        }
        return Visibility.Visible;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

补充:

在流程图中可能遇到多个元件串并联的情况,且元件个数不固定,因此就不适合用转换器绑定的情况。如下图的并联水泵:

因此需要将整个串并联单元视为一个整体,统一输出一个信号作为下一流程动画的输入控制。关于元件组的绘制,用ItemsControl就好了,不做过多讲解。元件组数据源是对ObservableCollection的一个封装,以并联集合为例:

public class OrGateObservableCollection : ObservableCollection<ElementData<bool>>
{
    private bool m_Output;
    public bool Output
    {
        get { return m_Output; }
        set
        {
            if (m_Output != value)
            {
                m_Output = value;
                OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(nameof(Output)));
            }
        }
    }

    public OrGateObservableCollection()
    {
        this.CollectionChanged += OrGateObservableCollection_CollectionChanged;
    }

    private void OrGateObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (var item in e.NewItems)
            {
                if (item is ElementData<bool> codeElement)
                {
                    codeElement.PropertyChanged += CodeElement_PropertyChanged;
                }
            }
            CodeElement_PropertyChanged(null, null);
        }
    }

    private void CodeElement_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        Output = this.Any(a => a.Value);
    }
}

public class ElementData<T> : BaseEntity where T : struct
{
    private string m_Name;
    public string Name
    {
        get { return m_Name; }
        set
        {
            if (m_Name != value)
            {
                m_Name = value;
                OnPropertyChanged(nameof(Name));
            }
        }
    }

    private T m_Value;
    public T Value
    {
        get { return m_Value; }
        set
        {
            if (default(T).Equals(m_Value) || (!default(T).Equals(m_Value) && !m_Value.Equals(value)))
            {
                m_Value = value;
                OnPropertyChanged(nameof(Value));
            }
        }
    }
}

如此,并联单元输出信号只需绑定到Output即可。

<think>嗯,用户想在WPF中实现管道流动的动画效果,也就是管道中液体流动的视觉效果。首先,我需要回忆一下WPF中常用的动画技术。用户之前可能已经看过一些相关的引用,比如使用转换器控制水流信号和路径动画的应用,所以可能需要结合这些方法。 首先,考虑基本的动画方法。用户可能需要让液体在管道中流动,这可以通过路径动画来实现,比如使用DoubleAnimationUsingPath沿着管道路径移动。不过,管道可能有不同的形状,直线或曲线,所以需要确定路径的几何结构。或者,可以使用线性渐变变换来模拟流动效果,比如通过移动渐变的起点和终点来创建流动的错觉。然后,用户可能需要更复杂的控制,比如根据阀门的开关来控制动画的播放和停止。这时候,可能需要使用故事板(Storyboard)来控制动画的状态,通过绑定到业务逻辑的变量,比如阀门的状态,来触发动画的开始或暂停。引用中提到使用转换器模拟逻辑门控制水流信号,这可能涉及到将阀门的状态通过值转换器转换为动画的播放状态。另外,考虑到性能,如果管道很多或者动画复杂,可能需要优化。比如,使用合成渲染层(RenderTargetBitmap)缓存静态部分,只对动态部分进行动画处理,这样可以减少CPU和GPU的负担。或者,使用位图缓存(CacheMode)来提高渲染效率。用户可能还需要动态生成管道,比如在流程图中根据数据生成不同的管道路径。这时候,可能需要结合PathGeometry来动态创建路径,并将动画绑定到这些动态生成的路径上。同时,确保动画能够正确应用在每条管道上,可能需要为每个管道元素单独创建和控制动画实例。 还有,用户提到参考了之前的路径动画,所以可能需要详细说明如何使用DoubleAnimationUsingPath,设置PathGeometry,以及如何控制动画的持续时间和循环方式。比如,定义一个Path对象,然后创建路径动画,将其应用到TranslateTransform的X和Y属性上,使元素沿着路径移动。另外,考虑到视觉效果,可能需要使用渐变或粒子效果来增强流动的真实感。比如,在Path元素上应用线性渐变画刷,并通过动画改变渐变的偏移量,模拟液体的流动。或者,使用粒子系统,生成小气泡或颗粒沿着管道移动,但这可能会更复杂,需要用到WriteableBitmap或第三方库。最后,需要提供示例代码,帮助用户快速实现。比如,创建一个简单的直线管道,应用路径动画,然后展示如何通过代码控制动画的启停。同时,提示用户如何扩展到更复杂的路径和动态生成的情况,以及如何与业务逻辑绑定,比如阀门状态改变时触发动画。 总结一下,可能的实现方法包括路径动画、渐变变换、动态路径生成、业务逻辑绑定和性能优化。需要分步骤解释这些方法,并提供代码示例,同时回答相关问题,比如如何动态生成管道、优化性能以及控制动画与业务逻辑的交互。</think>在WPF中实现管道流动动画效果,可通过以下三种典型方法实现: ### 一、路径动画基础实现 1. **定义管道几何路径**: ```xml <Path x:Name="pipePath" Data="M 0,100 L 400,100" Stroke="Gray" StrokeThickness="20"/> ``` 2. **创建流动元素**: ```xml <Ellipse x:Name="waterDrop" Width="10" Height="10" Fill="Blue"> <Ellipse.RenderTransform> <TranslateTransform/> </Ellipse.RenderTransform> </Ellipse> ``` 3. **配置路径动画**: ```xml <Storyboard x:Key="FlowAnimation"> <DoubleAnimationUsingPath Storyboard.TargetName="waterDrop" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)" Source="X" PathGeometry="{Binding Data, ElementName=pipePath}" Duration="0:0:3" RepeatBehavior="Forever"/> </Storyboard> ``` ### 二、渐变流动效果 通过动态改变LinearGradientBrush实现伪流动效果: ```csharp var gradient = new LinearGradientBrush( Colors.LightBlue, Colors.DarkBlue, 0); gradient.GradientStops.Add(new GradientStop(Colors.Transparent, 0.4)); var animation = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(2)) { RepeatBehavior = RepeatBehavior.Forever }; gradient.BeginAnimation(LinearGradientBrush.OffsetProperty, animation); ``` ### 三、动态路径绑定 结合业务逻辑控制动画状态: ```csharp var valveStateBinding = new Binding("IsValveOpen") { Converter = new BoolToAnimationStateConverter() }; Storyboard.SetBinding(Storyboard.CompletedCommand, valveStateBinding); ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值