• WPF中通过双击编辑DataGrid中Cell(附源码)


    背景

      在很多的时候我们需要编辑DataGrid中每一个Cell,编辑后保存数据,原生的WPF中的DataGrid并没有提供这样的功能,今天通过一个具体的例子来实现这一个功能,在这个例子中DataGrid中的数据类型可能是多种多样的,有枚举、浮点类型、布尔类型、DateTime类型,每一种不同的类型需要双击以后呈现不同的效果,本文通过使用Xceed.Wpf.DataGrid这个动态控件库来实现这个功能,当前使用的Dll版本是2.5.0.0,不同的版本可能实现上面有差别,这个在使用的时候需要特别注意。

    Demo预览

    代码结构

      代码还是按照常规的MVVM结构来进行编写,主要包括Views、Models、MainWindowViewModel、Converters这些常规的结构,在介绍这些之前来说一下我们的整体结构,在Demo中我们准备了一个四行三列的DataGrid,其中第一列主要是表示当前行的名称、后面的Step列是根据代码动态进行添加的,这个Step部分是我们通过代码动态调整的,调整完成后能够将数据保存到数据源中,我们还是按照MVVM的结构来进行进行代码的介绍。

      1 MainWindow

    <Window x:Class="DataGridCellDoubleClickDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:xceed="http://schemas.xceed.com/wpf/xaml/datagrid"
            xmlns:models="clr-namespace:DataGridCellDoubleClickDemo.Models"
            xmlns:views="clr-namespace:DataGridCellDoubleClickDemo.Views"
            mc:Ignorable="d"
            Title="DataGridDemo" Height="450" Width="800">
        <Window.Resources>
            <DataTemplate x:Key="CustomTemplate">
                <Border BorderThickness="1" BorderBrush="Blue">
                    <TextBlock Text="{Binding Path=Display }"  FontWeight="Bold" 
                               HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
                </Border>
            </DataTemplate>
            <DataTemplate x:Key="RowHeadTemplate" DataType="{x:Type models:RecipeControlVariable}">
                <TextBlock Text="{Binding DisplayName}"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Foreground="Black" FontSize="12"/>
            </DataTemplate>
            <xceed:DataGridCollectionViewSource x:Key="recipeData" Source="{Binding RecipeVariables}"></xceed:DataGridCollectionViewSource>
        </Window.Resources>
        <Grid>
            <xceed:DataGridControl x:Name="DataGridControl"
                                   AutoCreateColumns="False"
                                   FontSize="13"
                                   VerticalContentAlignment="Center"
                                   BorderBrush="Gray"
                                   BorderThickness="1"
                                   ItemsPrimaryAxis="Horizontal"
                                   PagingBehavior="LeftToRight"
                                   UpdateSourceTrigger="CellContentChanged"
                                   SelectionUnit="Cell"
                                   SelectionMode="Single"                               
                                   ItemsSource="{Binding  Source={StaticResource recipeData}}">
                <xceed:DataGridControl.View>
                    <xceed:TableflowView FixedColumnCount="1" ContainerHeight="30" x:Name="tblView" 
                                            VerticalGridLineThickness="0.5" HorizontalGridLineBrush="Gray" 
                                            HorizontalGridLineThickness="1" VerticalGridLineBrush="Black" 
                                            RowFadeInAnimationDuration="0" 
                                            ScrollingAnimationDuration="0" ColumnStretchMinWidth="10" 
                                            DetailIndicatorWidth="20" ShowRowSelectorPane="False" 
                                            ShowScrollTip="False" UseDefaultHeadersFooters="False">
                        <xceed:TableflowView.FixedHeaders>
                            <DataTemplate>
                                <xceed:ColumnManagerRow AllowColumnReorder="False" AllowColumnResize="True" AllowDrop="False" AllowSort="False" />
                            </DataTemplate>
                        </xceed:TableflowView.FixedHeaders>
                    </xceed:TableflowView>
                </xceed:DataGridControl.View>
    
                <xceed:DataGridControl.DefaultCellEditors>
                    <xceed:CellEditor x:Key="{x:Type models:SmartCellViewModel}">
                        <xceed:CellEditor.EditTemplate>
                            <DataTemplate>
                                <views:SmartCellEditor Content="{xceed:CellEditorBinding}"  VerticalAlignment="Center" 
                                                       Height="{Binding ActualHeight,RelativeSource={RelativeSource AncestorType={x:Type Border},AncestorLevel=1}}"></views:SmartCellEditor>
                            </DataTemplate>
                        </xceed:CellEditor.EditTemplate>
                    </xceed:CellEditor>
                </xceed:DataGridControl.DefaultCellEditors>
    
            </xceed:DataGridControl>
        </Grid>
    </Window>
    

      在View部分主要是通过引用Xceed中的DataGridControl控件进行扩展的,这个里面主要是需要设置DataGridControl的View和DefaultCellEditor这个里面DefaultCellEditor是本文的重点,这个就是单元格Cell双击后进行编辑的主体,在这个里面我们需要指定CellEditor的EditTemplate,这里面需要匹配一个DataTemplate,这个里面是一个SmartCellEditor的子View,下面我们来看看这个SmartCellEditor里面是什么内容?

      2 SmartCellEditor

    <UserControl x:Class="DataGridCellDoubleClickDemo.Views.SmartCellEditor"
                 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:conv="clr-namespace:DataGridCellDoubleClickDemo.Converters"
                 xmlns:xceed="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <UserControl.Resources>
            <conv:VisibilityConverter x:Key="visConverter" />
            <conv:TimeSpanConverter x:Key="timeSpanConverter" />
            <conv:NumConverter x:Key="numConverter" />
            <conv:BoolConverter x:Key="boolConverter" />
        </UserControl.Resources>
    
        <StackPanel Margin="0">
            <!--TextBlock-->
            <TextBlock x:Name="textBlock" 
                       Background="{Binding Background}" 
                       Foreground="{Binding Foreground}" 
                       Text="{Binding Path=Display,Mode=OneWay}"  
                       ToolTip="{Binding ToolTip}" 
                       FontWeight="{Binding FontWeight}" 
                       VerticalAlignment="Stretch"  
                       Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=TextBlock}"/>
    
    
            <!--Editable ComboBox-->
            <ComboBox x:Name="editableComboBox" ItemsSource="{Binding SmartCellData.Selections}" IsEditable="True"  VerticalAlignment="Stretch" VerticalContentAlignment="Center" 
                      DisplayMemberPath="SelectionDisplayName" Text="{Binding CellValue,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                      Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=EditableComboBox}" />
    
            <!--Readonly ComboBox-->
            <ComboBox x:Name="readonlyComboBox"  VerticalAlignment="Center" VerticalContentAlignment="Stretch"  ItemsSource="{Binding SmartCellData.Selections}" IsEditable="False"
                      DisplayMemberPath="SelectionDisplayName" SelectedValuePath="ControlName" SelectedValue="{Binding Path=CellValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
                      Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=ReadonlyComboBox}" />
    
    
            <!--Text Input TextBox-->
            <TextBox HorizontalContentAlignment="Left"  VerticalAlignment="Stretch" VerticalContentAlignment="Center" Text="{Binding CellValue}"
                     Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=TextBox}" TextAlignment="Left" />
    
    
            <!--Number Input TextBox-->
            <xceed:DecimalUpDown HorizontalContentAlignment="Left"  VerticalAlignment="Stretch" VerticalContentAlignment="Center"  FormatString="G" Value="{Binding Path=CellValue,Converter={StaticResource numConverter},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
                                 Increment="1" Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=DecimalUpDown}" TextAlignment="Left" />
    
    
            <!--CheckBox-->
            <CheckBox x:Name="checkBox"  VerticalAlignment="Stretch" VerticalContentAlignment="Center"  Content="{Binding Tag}" IsChecked="{Binding Path=CellValue,Converter={StaticResource boolConverter},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                      Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=CheckBox}" />
    
    
            <!--TimePicker-->
            <xceed:DateTimeUpDown Format="Custom" FormatString="HH:mm:ss"  VerticalAlignment="Stretch" VerticalContentAlignment="Center"  Value="{Binding Path=CellValue,Converter={StaticResource timeSpanConverter},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                                  Visibility="{Binding Path=., Converter={StaticResource visConverter},ConverterParameter=TimePicker}" CultureInfo="uk-UA" />
    
        </StackPanel>
    </UserControl>
    

      在这个里面我们在一个StackPanel中放置了匹配各种数据类型的Template,并且每一个的Visibility都是由visConverter这个自定义的Converter来实现的,后面我们会分析这个Converter里面的内容,这些代码的整体思想就是每次这个StackPanel里面的Template都只有一个可以显示,其它的都是隐藏的,哪一个会显示是根据当前的数据类型决定的,每一个注释表示每一个类型的数据,比如我们如果定义的是Bool类型,那么当我们双击单元格Cell的时候会出现一个CheckBox供我们编辑,所以这个里面我们需要根据我们定义的数据类型来扩展对应的模板,当我们双击单元格的时候就会显示这个模板从而进行编辑数据。

      3 MainWindowViewModel

    这个里面是定义的MainWindow对应的DataContext,在这里面我们会初始化绑定到MainWindow中DataGridControl的ItemsSource,我们先来看看这个里面核心的代码并就其中的要点进行分析。

    using DataGridCellDoubleClickDemo.Models;
    using System;
    using System.Collections.ObjectModel;
    using System.Windows;
    
    namespace DataGridCellDoubleClickDemo
    {
        public class MainWindowViewModel : NotificationObject
        {
            public MainWindowViewModel(Xceed.Wpf.DataGrid.DataGridControl dataGridControl)
            {
                DataGridControl = dataGridControl;
                InitRecipeVariables();
            }
    
    
            #region Properties
            private ObservableCollection<RecipeControlVariable> _RecipeVariables = new ObservableCollection<RecipeControlVariable>();
    
            public ObservableCollection<RecipeControlVariable> RecipeVariables
            {
                get { return _RecipeVariables; }
                set
                {
                    if (value != _RecipeVariables)
                    {
                        _RecipeVariables = value;
                        OnPropertyChanged(nameof(RecipeVariables));
                    }
    
                }
            }
    
            public Xceed.Wpf.DataGrid.DataGridControl DataGridControl { get; set; }
    
            #endregion
    
            #region Private Methods
            private void InitRecipeVariables()
            {
                _RecipeVariables.Add(new RecipeControlVariable
                {
                    ControlName = "Name",
                    DisplayName = "Name",
                    StepValues = new ObservableCollection<SmartCellViewModel>
                    {
                        new SmartCellViewModel
                        {
                            CellValue="Step",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "Name",
                                 DisplayName = "Name",
                                 VariableEditorType=RecipeVariableEditorType.TextInput
                            }
                        },
                        new SmartCellViewModel
                        {
                            CellValue="Step",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "Name",
                                 DisplayName = "Name",
                                 VariableEditorType=RecipeVariableEditorType.TextInput
                            }
                        }
                    }
    
                });
                _RecipeVariables.Add(new RecipeControlVariable
                {
                    ControlName = "Time",
                    DisplayName = "Process Time(Sec)",
                    StepValues = new ObservableCollection<SmartCellViewModel>
                    {
                        new SmartCellViewModel
                        {
                            CellValue="0",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "Time",
                                 DisplayName = "Process Time(Sec)",
                                 VariableEditorType=RecipeVariableEditorType.NumInput
                            }
                        },
                        new SmartCellViewModel
                        {
                            CellValue="0",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "Time",
                                 DisplayName = "Process Time(Sec)",
                                 VariableEditorType=RecipeVariableEditorType.NumInput
                            }
                        }
                    }
    
                });
                _RecipeVariables.Add(new RecipeControlVariable
                {
                    ControlName = "FrontChemical",
                    DisplayName = "FrontChemical",
                    StepValues = new ObservableCollection<SmartCellViewModel>
                    {
                        new SmartCellViewModel
                        {
                            CellValue="None",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "FrontChemical",
                                 DisplayName = "FrontChemical",
                                 VariableEditorType=RecipeVariableEditorType.ReadOnlySelection,
                                 Selections=new ObservableCollection<SelectionItem>
                                 {
                                     new SelectionItem
                                     {
                                         SelectionControlName="CHEM1",
                                         SelectionDisplayName="CHEM1",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="N2",
                                         SelectionDisplayName="N2",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="CDIW",
                                         SelectionDisplayName="CDIW",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="",
                                         SelectionDisplayName="None",
                                     }
                                 }
                            }
                        },
                        new SmartCellViewModel
                        {
                            CellValue="None",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "FrontChemical",
                                 DisplayName = "FrontChemical",
                                 VariableEditorType=RecipeVariableEditorType.ReadOnlySelection,
                                 Selections=new ObservableCollection<SelectionItem>
                                 {
                                     new SelectionItem
                                     {
                                         SelectionControlName="CHEM1",
                                         SelectionDisplayName="CHEM1",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="N2",
                                         SelectionDisplayName="N2",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="CDIW",
                                         SelectionDisplayName="CDIW",
                                     },
                                     new SelectionItem
                                     {
                                         SelectionControlName="",
                                         SelectionDisplayName="None",
                                     }
                                 }
                            }
                        }
                    }
    
                });
                _RecipeVariables.Add(new RecipeControlVariable
                {
                    ControlName = "NozzleBindingSetting",
                    DisplayName = "Nozzle Scan",
                    StepValues = new ObservableCollection<SmartCellViewModel>
                    {
                        new SmartCellViewModel
                        {
                            CellValue="Default",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "NozzleBindingSetting",
                                 DisplayName = "Nozzle Scan",
                                 VariableEditorType=RecipeVariableEditorType.TextInput
                            }
                        },
                        new SmartCellViewModel
                        {
                            CellValue="Default",
                            ErrorInfo=null,
                            SmartCellData=new RecipeVariableItem
                            {
                                 ControlName = "NozzleBindingSetting",
                                 DisplayName = "Nozzle Scan",
                                 VariableEditorType=RecipeVariableEditorType.TextInput
                            }
                        }
                    }
    
                });
            }
            #endregion
    
            /// <summary>
            /// reload datagrid content
            /// </summary>
            public void RefreshDataGrid()
            {
                try
                {
                    if (null == DataGridControl) return;
                    //generate columns in Grid
                    DataGridControl.CurrentColumn = null;
                    if (DataGridControl.Columns.Count > 0)
                        DataGridControl.Columns.Clear();
    
                    var template = (DataTemplate)this.DataGridControl.FindResource("CustomTemplate");
                    var rowTemplate = (DataTemplate)this.DataGridControl.FindResource("RowHeadTemplate");
    
                    DataGridControl.Columns.Add(new Xceed.Wpf.DataGrid.Column()
                    {
                        Width = 140,
                        Title = "Name",
                        FieldName = ".",
                        CellContentTemplate = rowTemplate
                    });
    
                    var cellEditor = DataGridControl.DefaultCellEditors[typeof(SmartCellViewModel)];
    
                    for (int index = 0; index < RecipeVariables[0].StepValues.Count; index++)
                    {
                        int width = 1;
                        for (int n = 0; n < RecipeVariables.Count; n++)
                        {
                            string display = RecipeVariables[n].StepValues[index].Display;
                            if (!string.IsNullOrWhiteSpace(display))
                            {
                                int temp = display.Length * 7;
                                width = Math.Max(temp, width);
                            }
                        }
                        width = (int)(width * 1.1);
                        width = Math.Max(width, 80);
                        DataGridControl.Columns.Add(new Xceed.Wpf.DataGrid.Column()
                        {
    
                            Title = string.Format("Step {0}", index + 1),
                            FieldName = string.Format("StepValues[{0}]", index),
                            CellContentTemplate = template,
                            AllowSort = false,
                            Width = width,
                            MaxWidth = width * 2,
                            CellEditor = cellEditor
                        });
                    }
    
                }
                catch (Exception ex)
                {
    
                }
            }
        }
    }
    

      在这个里面我们重点分析下RefreshDataGrid这个子函数,在我们的MainWindowViewModel中我们定义的RecipeVariables是最终绑定到MainWindow中定义的DataGridControl中的ItemsSource,是整个控件的数据源,由于我们这个DataGird的第一列和后面的Step列数据类型不同,所以我们的RefreshDataGrid函数中增加Column列的时候是分作两个部分,第一个部分是单独增加一列,后面的列是通过循环StepValues这个集合来动态进行增加的,代码中我们定义了多少个StepValue,那么后面就会有多少列,这个里面的重点是增加Column的时候FieldName的赋值,这个是十分关键的,这个关系到能够让每一列获取到正确的数据源,例如第一列赋值Filed= “.” 表示直接从当前绑定的数据源获取数据,另外后面的Step列的每一个FieldName是动态进行赋值的,赋值语句是:FieldName = string.Format("StepValues[{0}]", index),这个里面Index是一个动态值,这个是非常关键的一步,另外后面的Step列由于需要通过双击进行编辑所以每一个Column是需要赋值CellEditor对象的,另外这个ViewModel中的DataGridControl是通过构造函数进行赋值的,构造函数中的赋值就是MainWindow中定义的DataGridControl对象,这个在阅读代码时需要特别注意。

      4 Models

    Models主要是定义的数据集合,我们的代码中主要包括RecipeControlVariable和SmartViewModel这两个部分,这两个部分分别对应DataGridControl的数据源以及双击进行编辑的SmartCellEditor两个部分一一对应。

       更多代码方面的细节需要仔细去分析阅读源码,需要源码请点击行下载。

  • 相关阅读:
    mongodb入门安装与配置
    mssql export db
    初识django
    git
    水晶报表的使用经验和资料总结
    SQL中CONVERT转化函数的用法▲
    生活
    SQL中的临时表和表变量
    Convert Datetime to String in Sql Server
    转:探讨SQL Server 2005的安全策略
  • 原文地址:https://www.cnblogs.com/seekdream/p/14057633.html
Copyright © 2020-2023  润新知