基于C#WPF和OpencvSharp开发的图像处理系统——5.图像特效处理界面(主界面)

63 阅读5分钟

效果如下:

图像特效.gif

重点是Mvvm实现,而不是介绍图像的处理方法。

页面布局:Grid布局,两列。

第二列放一个StackPanel 里面放一个 输入图像控件和输出图像控件。

在viewModel中定义两个属性,供绑定

      #region 输入图像

        private string _srcImagePath;

        public string SrcImagePath
        {
            get => _srcImagePath;
            set
            {
                _srcImagePath = value;
                OnPropertyChanged();
            }
        }

        #endregion 输入图像

        #region 输出图像

        private Mat _dstMat;

        public Mat DstMat
        {
            get => _dstMat;
            set
            {
                _dstMat = value;
                OnPropertyChanged();
            }
        }

        #endregion 输出图像

xaml中绑定

<!--#region 第二列-->
        <Border Grid.Column="1" BorderBrush="#11aabd" BorderThickness="1,0,0,0" />
        <StackPanel Grid.Column="1" Orientation="Vertical">
            <local:SrcImgShowUserControl x:Name="srcImageUC" SrcImageSource="{Binding SrcImagePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <Border Height="2" BorderThickness="0,0,0,1" BorderBrush="#11aabd" />
            <local:DstImgShowUserControl x:Name="dstImageUC" Margin="0,1,0,2" DstImageSource="{Binding DstMat, Mode=TwoWay}" />
        </StackPanel>
        <!--#endregion-->

其中的Border是控件中间的分割线。

第一列的按钮操作部分,分为三类,是根据图像处理方法输入参数来分类的。

第一类 无其他参数输入

即方法只有一个输入参数为输入图像,例如下方法

        /// <summary>
        /// CvtColor颜色空间转Gray
        /// </summary>
        /// <param name="srcImg"></param>
        /// <returns></returns>
        public static Mat CvtColorToGray(Mat srcImg)
        {
            Mat dstImg = new Mat();
            Cv2.CvtColor(srcImg, dstImg, ColorConversionCodes.BGR2GRAY);
            return dstImg;
        }

        /// <summary>
        /// CvtColor颜色空间转HSV
        /// </summary>
        /// <param name="srcImg"></param>
        /// <returns></returns>
        public static Mat CvtColorToHSV(Mat srcImg)
        {
            Mat dstImg = new Mat();
            Cv2.CvtColor(srcImg, dstImg, ColorConversionCodes.BGR2HSV);
            return dstImg;
        }

自己进行了一些简单的封装。

viewmodel中定义属性

      public ObservableCollection<ImgOperateNoneParam> OperationsNoneParam { get; } = new();

ImgOperateNoneParam类:

    /// <summary>
    // 图像操作 无其他参数
    /// </summary>
    public class ImgOperateNoneParam
    {
        public string DisplayName { get; }
        public ImgOpNoneParamEnum OperationType { get; }
        public Func<Mat, Mat> Func { get; }

        public ImgOperateNoneParam(string displayName, ImgOpNoneParamEnum operationType, Func<Mat, Mat> func)
        {
            DisplayName = displayName;
            OperationType = operationType;
            Func = func;
        }
    }
    
        /// <summary>
    /// 图像单操作 无参数
    /// </summary>
    public enum ImgOpNoneParamEnum
    {
        /// <summary>
        /// 转为灰度图像
        /// </summary>
        CvtColorToGray,

        CvtColorToHSV,
        CvtColorToLab,
        CvtColorToRGB,
        Otsu,
        ...
      }

其中ImgOpNoneParamEnum 为每个方法对应一个枚举,用来界面按钮的CommandParameter绑定,就不需要定义对个按钮了,直接循环处理即可。

初始化方案集合

   private void InitImgOpNoneParam()
   {
       OperationsNoneParam.Add(new ImgOperateNoneParam("灰度图像", ImgOpNoneParamEnum.CvtColorToGray, ImageOperateMethods.CvtColorToGray));
       OperationsNoneParam.Add(new ImgOperateNoneParam("Lab空间", ImgOpNoneParamEnum.CvtColorToLab, ImageOperateMethods.CvtColorToLab));
       OperationsNoneParam.Add(new ImgOperateNoneParam("HSV空间", ImgOpNoneParamEnum.CvtColorToHSV, ImageOperateMethods.CvtColorToHSV));
       OperationsNoneParam.Add(new ImgOperateNoneParam("RGB图像", ImgOpNoneParamEnum.CvtColorToRGB, ImageOperateMethods.CvtColorToRGB));
       OperationsNoneParam.Add(new ImgOperateNoneParam("直方图均衡化", ImgOpNoneParamEnum.EqualizeHist, ImageOperateMethods.EqualizeHist));
       OperationsNoneParam.Add(new ImgOperateNoneParam("图像取反", ImgOpNoneParamEnum.Negation, ImageOperateMethods.Negation));
       OperationsNoneParam.Add(new ImgOperateNoneParam("Otsu二值化", ImgOpNoneParamEnum.Otsu, ImageOperateMethods.Otsu));
       ......
   }

命令处理

        public ICommand NoneParamOpCommand { get; }

        /// <summary>
        /// 图像处理 无参数方法
        /// </summary>
        /// <param name="operation"></param>
        private void ProcessImageNoneParam(ImgOpNoneParamEnum operation)
        {
            if (string.IsNullOrEmpty(_srcImagePath))
            {
                MessageBox.Show("未选择输入图像");
                return;
            }
            if (File.Exists(_srcImagePath) == false)
            {
                MessageBox.Show($"图像:{_srcImagePath}不存在");
                return;
            }
            Mat srcImg = new Mat(_srcImagePath);
            ImgOperateNoneParam singalOperate = OperationsNoneParam.First(x => x.OperationType == operation);
            if (singalOperate == null)
            {
                MessageBox.Show($"方法:{operation}未注册");
                return;
            }
            DstMat = singalOperate.Func(srcImg);
        }

xmal绑定 直接使用 ItemsControl即可

         <!--#region 无参数方法-->
         <ItemsControl Margin="40,20,0,0" ItemsSource="{Binding OperationsNoneParam}">
             <ItemsControl.ItemsPanel>
                 <ItemsPanelTemplate>
                     <WrapPanel Orientation="Horizontal" />
                 </ItemsPanelTemplate>
             </ItemsControl.ItemsPanel>
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
                     <Button
                         Width="140"
                         Style="{StaticResource ImgOperateBtnStyle}"
                         Command="{Binding DataContext.NoneParamOpCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                         Content="{Binding DisplayName}"
                         FontSize="16"
                         CommandParameter="{Binding OperationType}" />
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
         </ItemsControl>
         <!--#endregion-->

第二类 一个参数输入 int/double 类型参数

方法例如

        /// <summary>
        /// 图像添加指定数量的盐噪声点
        /// </summary>
        /// <param name="srcImg"></param>
        /// <param name="num"></param>
        /// <returns></returns>
        public static Mat AddSaltNosie(Mat srcImg, int num)
        {
            Mat dstImg = srcImg.Clone();
            Random r = new Random();
            for (int i = 0; i < num; i++)
            {
                int x = r.Next(srcImg.Rows);
                int y = r.Next(srcImg.Cols);
                dstImg.Set(x, y, new OpenCvSharp.Vec3b(255, 255, 255));
            }
            return dstImg;
        }

        /// <summary>
        /// 图像添加指定数量的椒噪声点
        /// </summary>
        /// <param name="srcImg"></param>
        /// <param name="num"></param>
        /// <returns></returns>
        public static Mat AddPepperNoise(Mat srcImg, int num)
        {
            Mat dstImg = srcImg.Clone();
            Random r = new Random();
            for (int i = 0; i < num; i++)
            {
                int x = r.Next(srcImg.Rows);
                int y = r.Next(srcImg.Cols);
                dstImg.Set(x, y, new OpenCvSharp.Vec3b(0, 0, 0));
            }
            return dstImg;
        }

        /// <summary>
        /// 图像添加指定数量的椒盐噪声点
        /// </summary>
        /// <param name="srcImg"></param>
        /// <param name="num"></param>
        /// <returns></returns>
        public static Mat AddSaltPepperNoise(Mat srcImg, int num)
        {
            OpenCvSharp.Vec3b[] SaltPepperNoises = new OpenCvSharp.Vec3b[2]
                                                    {  new OpenCvSharp.Vec3b(0, 0, 0), // 椒噪声
                                                       new OpenCvSharp.Vec3b(255, 255, 255) // 盐噪声
                                                    };
            Mat dstImg = srcImg.Clone();
            Random r = new Random();
            for (int i = 0; i < num; i++)
            {
                int x = r.Next(srcImg.Rows);
                int y = r.Next(srcImg.Cols);
                int index = r.Next(0, 2);
                Vec3b nosie = SaltPepperNoises[index];
                dstImg.Set(x, y, nosie);
            }
            return dstImg;
        }
        ....

viewmodel中类似于第一类无参数的定义

   /// <summary>
   /// 图像操作集合 一个参数 数值类
   /// </summary>
   public ObservableCollection<ImgOpOneParamNumBase> OperationsOneParamNum { get; } = new();

类定义

    public class ImgOperateOneParamNum<T> : ImgOpOneParamNumBase where T : INumber<T>
    {
        public ImgOperateOneParamNum(string buttonText, string labelText, ImgOpOneParamNumEnum operationType, Func<Mat, T, Mat> func, string toolTips = "", string defeatValue = "0")
            : base()
        {
            ButtonText = buttonText;
            LabelText = labelText;
            Func = func;
            OperationType = operationType;
            InputParam = defeatValue;
            ParamToolText = toolTips;
        }

        public Func<Mat, T, Mat> Func { get; }

        public override bool CheckParamFormat()
        {
            if (string.IsNullOrWhiteSpace(InputParam))
            {
                InputParam = "0";
                return true;
            }

            if (typeof(T) == typeof(int))
            {
                if (int.TryParse(InputParam, out int intValue))
                {
                    return intValue >= 0;
                }
            }
            else if (typeof(T) == typeof(double))
            {
                if (double.TryParse(InputParam, out double doubleValue))
                {
                    return doubleValue >= 0;
                }
            }
            else if (typeof(T) == typeof(float))
            {
                if (float.TryParse(InputParam, out float floatValue))
                {
                    return floatValue >= 0;
                }
            }
            return false;
        }

        public override Mat Execute(Mat srcImg)
        {
            T para = ConvertInputValue(InputParam);
            return Func(srcImg, para);
        }

        private T ConvertInputValue(string input)
        {
            if (string.IsNullOrWhiteSpace(input))
            {
                return T.Zero;
            }

            if (typeof(T) == typeof(int))
            {
                if (int.TryParse(input, out int intValue))
                    return (T)(object)intValue;
            }
            else if (typeof(T) == typeof(double))
            {
                if (double.TryParse(input, out double doubleValue))
                    return (T)(object)doubleValue;
            }
            else if (typeof(T) == typeof(float))
            {
                if (float.TryParse(input, out float floatValue))
                    return (T)(object)floatValue;
            }

            throw new ArgumentException($"无法将 '{input}' 转换为 {typeof(T).Name}");
        }
    }
    
        /// <summary>
    /// 图像单操作 一个参数  数字
    /// </summary>
    public enum ImgOpOneParamNumEnum
    {
        /// <summary>
        /// 素描
        /// </summary>
        Sketch,

        /// <summary>
        /// 浮雕
        /// </summary>
        Emboss,

        /// <summary>
        /// 快速去雾
        /// </summary>
        FastDehazing,

        /// <summary>
        /// 毛玻璃
        /// </summary>
        GroundGlass,

        /// <summary>
        /// 透明度
        /// </summary>
        Lucency,

        /// <summary>
        /// 加盐噪声
        /// </summary>
        AddSaltNoise,
        ...
        }

基类 同时供第三类方法使用

public abstract class ImgOpOneParamNumBase : ViewModelBase
{
    public string ButtonText { get; protected set; }
    public string LabelText { get; protected set; }
    public ImgOpOneParamNumEnum OperationType { get; protected set; }

    public string ParamToolText { get; protected set; }

    private string _inputParam;

    public string InputParam
    {
        get => _inputParam;
        set
        {
            _inputParam = value;
            OnPropertyChanged();
        }
    }

    /// <summary>
    /// 执行方法
    /// </summary>
    /// <param name="srcImg"></param>
    /// <returns></returns>
    public abstract Mat Execute(Mat srcImg);

    /// <summary>
    /// 检查格式是否正确
    /// </summary>
    /// <returns></returns>
    public abstract bool CheckParamFormat();

    private string[] _itemsArray = Array.Empty<string>();

    public string[] ItemsArray
    {
        get => _itemsArray;
        set
        {
            _itemsArray = value;
            OnPropertyChanged();
        }
    }
}

集合初始化和命令

        private void InitImgOpOneParamNum()
        {
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "素描", "像素值",
                ImgOpOneParamNumEnum.Sketch, ImageOperateMethods.Sketch,
                "Int类型", "128"));
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "浮雕", "像素值",
                ImgOpOneParamNumEnum.Emboss, ImageOperateMethods.Emboss,
                "", "128"));
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "毛玻璃", "邻域大小",
                ImgOpOneParamNumEnum.GroundGlass, ImageOperateMethods.GroundGlass,
                "int类型,奇数,大于3", "5"));
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "加盐噪声", "噪声点数",
                ImgOpOneParamNumEnum.AddSaltNoise, ImageOperateMethods.AddSaltNosie,
                "", "2500"));
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "加椒噪声", "噪声点数",
                ImgOpOneParamNumEnum.AddPepperNoise, ImageOperateMethods.AddPepperNoise,
                "", "2500"));
            OperationsOneParamNum.Add(new ImgOperateOneParamNum<int>(
                "加椒盐噪声", "噪声点数",
                ImgOpOneParamNumEnum.AddSaltPepperNoise, ImageOperateMethods.AddSaltPepperNoise,
                "", "2500"));
                .....
        }

        public ICommand OneParamNumOpCommand { get; }

        /// <summary>
        /// 图像处理 一个参数方法 数值类
        /// </summary>
        /// <param name="operation"></param>
        private void ProcessImageOneParamNum(ImgOpOneParamNumEnum operation)
        {
            if (string.IsNullOrEmpty(_srcImagePath))
            {
                MessageBox.Show("未选择输入图像");
                return;
            }
            if (File.Exists(_srcImagePath) == false)
            {
                MessageBox.Show($"图像:{_srcImagePath}不存在");
                return;
            }
            Mat srcImg = new Mat(_srcImagePath);
            ImgOpOneParamNumBase operate = OperationsOneParamNum.First(x => x.OperationType == operation);
            if (operate == null)
            {
                MessageBox.Show($"方法:{operation}未注册");
                return;
            }
            if (operate.CheckParamFormat() == false)
            {
                MessageBox.Show($"参数:{operate.LabelText} 格式错误");
                return;
            }
            DstMat = operate.Execute(srcImg);
        }

xaml绑定

  <!--#region 一个参数 int 或 double-->
  <ScrollViewer Grid.Row="1" MaxHeight="540" VerticalScrollBarVisibility="Auto">
      <ItemsControl ItemsSource="{Binding OperationsOneParamNum}">
          <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                  <StackPanel Orientation="Vertical" />
              </ItemsPanelTemplate>
          </ItemsControl.ItemsPanel>
          <ItemsControl.ItemTemplate>
              <DataTemplate>
                  <Border BorderBrush="#11aabd" BorderThickness="1,1,1,1">
                      <StackPanel Orientation="Horizontal">
                          <TextBlock
                              Grid.Column="0"
                              Width="90"
                              Margin="10,0,10,0"
                              VerticalAlignment="Center"
                              FontSize="16"
                              Foreground="White"
                              TextAlignment="Center"
                              Text="{Binding LabelText}" />
                          <TextBox
                              Grid.Column="1"
                              Width="50"
                              Margin="0,0,10,0"
                              Padding="5,2"
                              VerticalAlignment="Center"
                              TextAlignment="Center"
                              ToolTip="{Binding ParamToolText, Converter={StaticResource emptyStringToNullConverter}}"
                              Text="{Binding InputParam, UpdateSourceTrigger=PropertyChanged}" />

                          <Button
                              Grid.Column="2"
                              Width="120"
                              Height="30"
                              Margin="0,5,10,5"
                              VerticalAlignment="Center"
                              FontSize="16"
                              Style="{StaticResource ImgOperateBtnStyle}"
                              Content="{Binding ButtonText}"
                              Command="{Binding DataContext.OneParamNumOpCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                              CommandParameter="{Binding OperationType}" />
                      </StackPanel>
                  </Border>
              </DataTemplate>
          </ItemsControl.ItemTemplate>
      </ItemsControl>
  </ScrollViewer>
  <!--#endregion-->

第三类 一个参数输入 枚举 类型参数

方法如下

        /// <summary>
        /// 图像伪颜色加强
        /// </summary>
        /// <param name="srcImg"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public static Mat ApplyColorMap(Mat srcImg, OpenCvSharp.ColormapTypes types)
        {
            Mat dstImg = new Mat();
            Cv2.ApplyColorMap(srcImg, dstImg, types);
            return dstImg;
        }
        /// <summary>
        /// 图像向上或向下翻转半
        /// </summary>
        /// <param name="srcImg"></param>
        /// <param name="upOrDown"></param>
        /// <returns></returns>
        public static Mat CompleteSymm(Mat srcImg, SymmDirection direction)
        {
            //转化成方阵
            int ss = Math.Min(srcImg.Rows, srcImg.Cols);
            OpenCvSharp.Size size = new OpenCvSharp.Size(ss, ss);
            Cv2.Resize(srcImg, srcImg, size);

            bool direct = direction == SymmDirection.LowerToUpper;
            Cv2.CompleteSymm(srcImg, direct);
            return srcImg;
        }

viewmodel定义

        /// <summary>
        /// 图像操作集合 一个参数 枚举
        /// </summary>
        public ObservableCollection<ImgOpOneParamEnumBase> OperationsOneParamEnum { get; } = new();

        private void InitImgOpOneParamEnum()
        {
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<FlipMode>(
                "反转", "方向",
                ImgOpOneParamEnumEnum.Flip, ImageOperateMethods.Flip,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<ColormapTypes>(
                "伪颜色增强", "方式",
                ImgOpOneParamEnumEnum.ApplyColorMap, ImageOperateMethods.ApplyColorMap,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<EdegTypeEnum>(
                "边缘检测", "检测算法",
                ImgOpOneParamEnumEnum.Edge, ImageOperateMethods.Edge,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<ClassifierEnum>(
                "特征检测", "分类器",
                ImgOpOneParamEnumEnum.FeatureRecogn, ImageOperateMethods.FeatureRecogn,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<SkinDefectEnum>(
                "皮肤检测", "算法",
                ImgOpOneParamEnumEnum.SkinDefect, ImageOperateMethods.SkinDefect,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<SymmDirection>(
                "斜对折", "方向",
                ImgOpOneParamEnumEnum.CompleteSymm, ImageOperateMethods.CompleteSymm,
                ""));
            OperationsOneParamEnum.Add(new ImgOperateOneParamEnum<BGREnum>(
                "单通道显示", "通道",
                ImgOpOneParamEnumEnum.BGRSingle, ImageOperateMethods.BGRSingle,
                ""));
        }

        public ICommand OneParamEnumOpCommand { get; }

        /// <summary>
        /// 图像处理 一个参数方法 数值类
        /// </summary>
        /// <param name="operation"></param>
        private void ProcessImageOneParamEnum(ImgOpOneParamEnumEnum operation)
        {
            if (string.IsNullOrEmpty(_srcImagePath))
            {
                MessageBox.Show("未选择输入图像");
                return;
            }
            if (File.Exists(_srcImagePath) == false)
            {
                MessageBox.Show($"图像:{_srcImagePath}不存在");
                return;
            }
            Mat srcImg = new Mat(_srcImagePath);
            ImgOpOneParamEnumBase operate = OperationsOneParamEnum.First(x => x.OperationType == operation);
            if (operate == null)
            {
                MessageBox.Show($"方法:{operation}未注册");
                return;
            }
            if (operate.CheckParamFormat() == false)
            {
                MessageBox.Show($"参数:{operate.LabelText} 格式错误");
                return;
            }
            DstMat = operate.Execute(srcImg);
        }

ImgOperateOneParamEnum类

    public class ImgOperateOneParamEnum<T> : ImgOpOneParamEnumBase where T : Enum
    {
        public ImgOperateOneParamEnum(string buttonText, string labelText, ImgOpOneParamEnumEnum operationType, Func<Mat, T, Mat> func, string toolTips = "", string defeatValue = "0")
            : base()
        {
            ButtonText = buttonText;
            LabelText = labelText;
            Func = func;
            OperationType = operationType;
            InputParam = defeatValue;
            ParamToolText = toolTips;
            ItemsArray = Enum.GetNames(typeof(T));
        }

        public Func<Mat, T, Mat> Func { get; }

        public override bool CheckParamFormat()
        {
            int index = Array.IndexOf(ItemsArray, InputParam);
            return index >= 0;
        }

        public override Mat Execute(Mat srcImg)
        {
            T para = ConvertInputValue(InputParam);
            return Func(srcImg, para);
        }

        private T ConvertInputValue(string input)
        {
            if (Enum.TryParse(typeof(T), input, true, out object result))
            {
                return (T)result;
            }
            //throw new ArgumentException($"无法将 '{input}' 转换为 {typeof(T).Name}");
            return default;
        }
    }
        /// <summary>
    /// 图像单操作 一个参数  枚举
    /// </summary>
    public enum ImgOpOneParamEnumEnum
    {
        Flip,
        ApplyColorMap,
        Edge,

        /// <summary>
        /// 特征检测
        /// </summary>
        FeatureRecogn,

        /// <summary>
        /// 皮肤检测
        /// </summary>
        SkinDefect,

        /// <summary>
        /// 图像斜折叠
        /// </summary>
        CompleteSymm,

        BGRSingle,
    }

xaml绑定

     <ScrollViewer
         Grid.Row="1"
         MaxHeight="600"
         Margin="20,0,0,0"
         VerticalScrollBarVisibility="Auto">
         <ItemsControl ItemsSource="{Binding OperationsOneParamEnum}">
             <ItemsControl.ItemsPanel>
                 <ItemsPanelTemplate>
                     <StackPanel Orientation="Vertical" />
                 </ItemsPanelTemplate>
             </ItemsControl.ItemsPanel>
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
                     <Border BorderBrush="#11aabd" BorderThickness="1,1,1,1">
                         <StackPanel Orientation="Horizontal">
                             <TextBlock
                                 Grid.Column="0"
                                 Width="80"
                                 Margin="10,0,10,0"
                                 VerticalAlignment="Center"
                                 FontSize="18"
                                 Foreground="White"
                                 TextAlignment="Center"
                                 Text="{Binding LabelText}" />
                             <ComboBox
                                 Grid.Column="1"
                                 Width="150"
                                 Height="25"
                                 Margin="0,0,10,0"
                                 Padding="5,2"
                                 HorizontalAlignment="Center"
                                 VerticalAlignment="Center"
                                 IsEditable="False"
                                 ItemsSource="{Binding ItemsArray}"
                                 SelectedIndex="0"
                                 Style="{StaticResource ParameterizedComboBoxStyle}"
                                 Background="#495660"
                                 BorderBrush="White"
                                 Foreground="White"
                                 ToolTip="{Binding ParamToolText, Converter={StaticResource emptyStringToNullConverter}}"
                                 Text="{Binding InputParam, UpdateSourceTrigger=PropertyChanged}" />
                             <Button
                                 Grid.Column="2"
                                 Width="100"
                                 Height="30"
                                 Margin="0,5,10,5"
                                 VerticalAlignment="Center"
                                 FontSize="16"
                                 Style="{StaticResource ImgOperateBtnStyle}"
                                 Content="{Binding ButtonText}"
                                 Command="{Binding DataContext.OneParamEnumOpCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                 CommandParameter="{Binding OperationType}" />
                         </StackPanel>
                     </Border>
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
         </ItemsControl>
     </ScrollViewer>