前言
在使用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();
}
}