簡體   English   中英

DataGrid 或 GridView CellTemplate 中的動態內容

[英]Dynamic content in DataGrid or GridView CellTemplate

我正在嘗試使用DataGrid實現一個視圖,以顯示來自 controller 的實時數據,並有可能將列表中的控件中的數據寫回 controller。 應根據數據信號的數據類型動態選擇控件。 在我目前的方法中,如果數據類型是 boolean,我使用按鈕,否則單元格中的控件應該是 slider。

問題是我只得到一個可見實例,每個不同的控件。

我嘗試使用DataGrid控件和帶有GridViewListView ,如您在 2 個選項卡中所見:

列表視圖選項卡。

數據網格選項卡。

如果我在GridView上單擊兩次,控件將在所選行中可見,並在另一行中消失。 ListView中,如果我單擊任何沒有可見控件的單元格,則不會發生任何事情。

在本示例中,我使用的是XAML 工具包中的材料設計,但我在不添加它的情況下得到了相同的結果。

我想我不能像在視圖中那樣使用CellTemplate ,但我希望有辦法讓它工作。

代碼:

主窗口.xaml

<Window x:Class="ListContentControlTest.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:local="clr-namespace:ListContentControlTest"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        TextElement.FontWeight="Regular"
        TextElement.FontSize="13"
        TextOptions.TextFormattingMode="Ideal" 
        TextOptions.TextRenderingMode="Auto"
        Background="{DynamicResource MaterialDesignPaper}"
        FontFamily="{materialDesign:MaterialDesignFont}" 
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
   <Grid VerticalAlignment="Stretch" HorizontalAlignment='Stretch'>
      <TabControl>
         <TabItem Header="ListView">
            <ListView ItemsSource="{Binding DataSignalsList}">
               <ListView.View>
                  <GridView>
                     <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"  Width="200" />
                     <GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}" Width="200" />
                     <GridViewColumn Header="Read" DisplayMemberBinding="{Binding Value}" Width="100" />
                     <GridViewColumn Header="Write">
                        <GridViewColumn.CellTemplate>
                           <DataTemplate>
                              <ContentControl>
                                 <ContentControl.Style>
                                    <Style TargetType="ContentControl">
                                       <Style.Triggers>
                                          <DataTrigger Binding="{Binding IsDataTypeBool}" Value="true">
                                             <Setter Property="Content">
                                                <Setter.Value>
                                                   <Button Content="Click Me" FontSize="10" Height="18"/>
                                                </Setter.Value>
                                             </Setter>
                                          </DataTrigger>
                                          <DataTrigger Binding="{Binding IsDataTypeBool}" Value="false">
                                             <Setter Property="Content">
                                                <Setter.Value>
                                                   <StackPanel Orientation="Horizontal">
                                                      <TextBlock Text="0" Margin="10 5 10 5"/>
                                                      <Slider Value="50" Minimum="0" Maximum="100" MinWidth="150"/>
                                                      <TextBlock Text="100" Margin="10 5 10 5"/>
                                                   </StackPanel>
                                                </Setter.Value>
                                             </Setter>
                                          </DataTrigger>
                                       </Style.Triggers>
                                    </Style>
                                 </ContentControl.Style>
                              </ContentControl>
                           </DataTemplate>
                        </GridViewColumn.CellTemplate>
                     </GridViewColumn>

                  </GridView>
               </ListView.View>
            </ListView>
         </TabItem>
         <TabItem Header="GridView">
            <DataGrid 
                    ItemsSource="{Binding DataSignalsList}"
                    CanUserSortColumns="True"
                    CanUserResizeRows="False"
                    CanUserAddRows="False"
                    AutoGenerateColumns="False"
                    HeadersVisibility="All" >
               <DataGrid.Columns>
                  <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                  <DataGridTextColumn Header="Address" Binding="{Binding Address}" />
                  <DataGridTextColumn CanUserSort="False" CanUserReorder="False" Header="Read" Binding="{Binding Value}"/>
                  <DataGridTemplateColumn Header="Write" MinWidth="250">
                     <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                           <ContentControl>
                              <ContentControl.Style>
                                 <Style TargetType="ContentControl">
                                    <Style.Triggers>
                                       <DataTrigger Binding="{Binding IsDataTypeBool}" Value="true">
                                          <Setter Property="Content">
                                             <Setter.Value>
                                                <StackPanel Orientation="Horizontal" Margin="5">
                                                   <Button Content="Click Me" FontSize="10" Height="18"/>
                                                </StackPanel>
                                             </Setter.Value>
                                          </Setter>
                                       </DataTrigger>
                                       <DataTrigger Binding="{Binding IsDataTypeBool}" Value="false">
                                          <Setter Property="Content">
                                             <Setter.Value>
                                                <StackPanel Orientation="Horizontal">
                                                   <TextBlock Text="0" Margin="10 5 10 5"/>
                                                   <Slider Value="50" Minimum="0" Maximum="100" MinWidth="150"/>
                                                   <TextBlock Text="100" Margin="10 5 10 5"/>
                                                </StackPanel>
                                             </Setter.Value>
                                          </Setter>
                                       </DataTrigger>
                                    </Style.Triggers>
                                 </Style>
                              </ContentControl.Style>
                           </ContentControl>
                        </DataTemplate>
                     </DataGridTemplateColumn.CellTemplate>
                  </DataGridTemplateColumn>
               </DataGrid.Columns>
            </DataGrid>
         </TabItem>
      </TabControl>
   </Grid>
</Window>

主窗口.xaml.cs

using System.Windows;

namespace ListContentControlTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

MainWindowViewModel.cs

using ListContentControlTest.Models;
using System.Collections.Generic;

namespace ListContentControlTest.ViewModels
{
    public class MainWindowViewModel
    {
        private IList<DataSignalModel> _dataSignalsList;

        public MainWindowViewModel()
        {
            DataSignalsList = new List<DataSignalModel>
            {
                new DataSignalModel{Name = "Data Signal 0", Address = "Float Data Type Address", DataType = DataType.Float},
                new DataSignalModel{Name = "Data Signal 1", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 2", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 3", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 4", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 5", Address = "Float Data Type Address", DataType = DataType.Float},
                new DataSignalModel{Name = "Data Signal 6", Address = "Float Data Type Address", DataType = DataType.Float},
                new DataSignalModel{Name = "Data Signal 7", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 8", Address = "Bool Data Type Address", DataType = DataType.Bool},
                new DataSignalModel{Name = "Data Signal 9", Address = "Bool Data Type Address", DataType = DataType.Bool}
            };
        }
        public IList<DataSignalModel> DataSignalsList
        {
            get { return _dataSignalsList; }
            set { _dataSignalsList = value; }
        }
    }
}

數據信號模型.cs

using System.ComponentModel;

namespace ListContentControlTest.Models
{
    public enum DataType
    {
        Bool,
        Int,
        Float
    }

    public class DataSignalModel : INotifyPropertyChanged
    {
        bool _isDataTypeBool;
        private DataType _dataType;

        public string Name { get; set; }

        public DataType DataType
        {
            get => _dataType;
            set
            {
                _dataType = value;
                if (_dataType == DataType.Bool)
                {
                    IsDataTypeBool = true;
                }
                else
                {
                    IsDataTypeBool = false;
                }

                OnPropertyChanged(nameof(DataType));
                OnPropertyChanged(nameof(IsDataTypeBool));
            }
        }

        public string Address { get; set; }
      
        public double Value { get; set; }

        public bool IsDataTypeBool
        {
            get => _isDataTypeBool;
            set
            {
                _isDataTypeBool = value;
                OnPropertyChanged(nameof(IsDataTypeBool));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }
}

App.xaml

<Application x:Class="ListContentControlTest.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:ListContentControlTest">
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.LightBlue.xaml" />
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Purple.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

App.xaml.cs

using System.Windows;
using ListContentControlTest.ViewModels;

namespace ListContentControlTest
{
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            MainWindow window = new MainWindow();
            MainWindowViewModel mwvm = new MainWindowViewModel();
            window.DataContext = mwvm;
            window.Show();
        }
    }
}

您看到的行為是由您創建數據模板的方式引起的。 里面的ContentControl是為每個單元格實例化的,但是 styles 的 setter 里面的控件不是。 這意味着按鈕和 slider 的單個實例在所有單元之間共享。 作為 WPF 中的控件只能有一個父級 - 或者換句話說只能在可視樹中出現一次 - 它只能在單元格之間重新分配或移動。

為了解決這個問題,不要在樣式中創建控件,而是在數據模板中創建控件,例如:

<!-- Data template for "Bool" -->
<DataTemplate x:Key="DataSignalModelBoolTemplate" DataType="{x:Type local:DataSignalModel}">
   <StackPanel Orientation="Horizontal" Margin="5">
      <Button Content="Click Me" FontSize="10" Height="18"/>
   </StackPanel>
</DataTemplate>

<!-- Data template for both "Int" and "Float", hence "Numeric" -->
<DataTemplate x:Key="DataSignalModelNumericTemplate" DataType="{x:Type local:DataSignalModel}">
   <StackPanel Orientation="Horizontal">
      <TextBlock Text="0" Margin="10 5 10 5"/>
      <Slider Value="50" Minimum="0" Maximum="100" MinWidth="150"/>
      <TextBlock Text="100" Margin="10 5 10 5"/>
   </StackPanel>
</DataTemplate>

當您存儲數據類型成員而不是創建兩個不同的 model 類型時,您將必須創建一個自定義DataTemplateSelector來根據DataType選擇數據模板。

public class DataSignalModelTemplateSelector : DataTemplateSelector
{
   public override DataTemplate SelectTemplate(object item, DependencyObject container)
   {
      if (!(item is DataSignalModel dataSignalModel) || !(container is FrameworkElement containerFrameworkElement))
         return null;

      switch (dataSignalModel.DataType)
      {
         case DataType.Bool:
            return FindDataTemplate(containerFrameworkElement, "DataSignalModelBoolTemplate");
         case DataType.Int:
         case DataType.Float:
            return FindDataTemplate(containerFrameworkElement, "DataSignalModelNumericTemplate");
         default:
            throw new ArgumentOutOfRangeException();
      }
   }

   private static DataTemplate FindDataTemplate(FrameworkElement frameworkElement, string key)
   {
      return (DataTemplate)frameworkElement.FindResource(key);
   }
}

在資源字典中創建此轉換器的實例,您也可以在其中存儲數據模板。

<local:DataSignalModelTemplateSelector x:Key="DataSignalModelTemplateSelector"/>

然后,像這樣調整ListViewDataGrid中的列定義:

<GridViewColumn Header="Write" CellTemplateSelector="{StaticResource DataSignalModelTemplateSelector}"/>
<DataGridTemplateColumn Header="Write" MinWidth="250" CellTemplateSelector="{StaticResource DataSignalModelTemplateSelector}"/>

就這樣。 數據模板選擇器將自動確定適當的模板並在資源中搜索它。 您可以從DataSignalModel中刪除IsDataTypeBool ,因為它不是必需的。

為方便起見,這里是MainWindow的完整標記:

<Window x:Class="ListContentControlTest.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:local="clr-namespace:ListContentControlTest"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        TextElement.Foreground="{DynamicResource MaterialDesignBody}"
        TextElement.FontWeight="Regular"
        TextElement.FontSize="13"
        TextOptions.TextFormattingMode="Ideal" 
        TextOptions.TextRenderingMode="Auto"
        Background="{DynamicResource MaterialDesignPaper}"
        FontFamily="{materialDesign:MaterialDesignFont}" 
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
   <Window.Resources>
      <local:DataSignalModelTemplateSelector x:Key="DataSignalModelTemplateSelector"/>
      <DataTemplate x:Key="DataSignalModelBoolTemplate" DataType="{x:Type local:DataSignalModel}">
         <StackPanel Orientation="Horizontal" Margin="5">
            <Button Content="Click Me" FontSize="10" Height="18"/>
         </StackPanel>
      </DataTemplate>
      <DataTemplate x:Key="DataSignalModelNumericTemplate" DataType="{x:Type local:DataSignalModel}">
         <StackPanel Orientation="Horizontal">
            <TextBlock Text="0" Margin="10 5 10 5"/>
            <Slider Value="50" Minimum="0" Maximum="100" MinWidth="150"/>
            <TextBlock Text="100" Margin="10 5 10 5"/>
         </StackPanel>
      </DataTemplate>
   </Window.Resources>
   <Grid VerticalAlignment="Stretch" HorizontalAlignment='Stretch'>
      <TabControl>
         <TabItem Header="ListView">
            <ListView ItemsSource="{Binding DataSignalsList}">
               <ListView.View>
                  <GridView>
                     <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"  Width="200" />
                     <GridViewColumn Header="Address" DisplayMemberBinding="{Binding Address}" Width="200" />
                     <GridViewColumn Header="Read" DisplayMemberBinding="{Binding Value}" Width="100" />
                     <GridViewColumn Header="Write" CellTemplateSelector="{StaticResource DataSignalModelTemplateSelector}"/>
                  </GridView>
               </ListView.View>
            </ListView>
         </TabItem>
         <TabItem Header="GridView">
            <DataGrid 
                ItemsSource="{Binding DataSignalsList}"
                CanUserSortColumns="True"
                CanUserResizeRows="False"
                CanUserAddRows="False"
                AutoGenerateColumns="False"
                HeadersVisibility="All" >
               <DataGrid.Columns>
                  <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
                  <DataGridTextColumn Header="Address" Binding="{Binding Address}" />
                  <DataGridTextColumn CanUserSort="False" CanUserReorder="False" Header="Read" Binding="{Binding Value}"/>
                  <DataGridTemplateColumn Header="Write" MinWidth="250" CellTemplateSelector="{StaticResource DataSignalModelTemplateSelector}"/>
               </DataGrid.Columns>
            </DataGrid>
         </TabItem>
      </TabControl>
   </Grid>
</Window>

關於數據類型的說明:您在DataSignalModel中保留一個成員DataType以確定其類型。 通常您會為每個數據創建專門的類型,而不是為所有的 model,例如:

  • BoolDataSignalModel
  • IntDataSignalModel
  • FloatDataSignalModel

從設計的角度來看,這也有利於分離關注點。 這些類型還可能會公開不適用於所有數據變體的獨特屬性、方法和事件。 此外,如果您可以依賴具體類型,它會大大簡化綁定和模板化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM