【问题标题】:Show/Hide DataGrid columns based on List content根据列表内容显示/隐藏 DataGrid 列
【发布时间】:2019-12-09 21:56:00
【问题描述】:

我正在编写一个WPF 应用程序来显示在大型系统中生成的日志文件,这将是其中的一部分。日志文件是.CSV 文件,并且将具有预定义的列数。但是,这些文件中隐藏着“子类型”。让我详细说明。

为简洁起见,假设我的文件有 4 列,即:

LineNum、Data1、Data2、Data3

但这里有一个问题。我们的Type1 日志文件将包含与LineNum, Data1, and Data2 相关的数据,而我们的Type2 日志文件将包含与LineNum, Data1, and Data3 相关的数据。请记住,这只是一个示例,我实际上有超过 4 列,并且大约有 20 种不同的组合。以下是每种类型日志的两个示例。

类型1

1,Data1A,Data2A,,

2,Data1B,Data2B,,,

3,Data1C,Data2C,,,

类型2

1,Data1A,,Data3A,

2,Data1B,,Data3B,

3,Data1C,,Data3C,

正如我之前所说,我的实际文件有大量列(70 多列),但每种类型的数据仅与少数列有关。我完全无法控制它,因为它是已经存在的旧较大系统的一部分。现在我想让我的应用程序读取这些文件,但只显示其中包含数据的列。

幸运的是,在更大的系统中,有一组“系统”文件指示哪种类型的文件在哪些列中有数据。因此,Type1sys 文件将如下所示:

1、1、1、0

1 表示除第 3 列 (Data3) 之外的所有列都有数据。而Type2sys 文件将如下所示:

1、1、0、1

表示除Data2 之外的所有内容都可用。

现在,用户通过文件选择对话框选择要显示的文件类型。当他们这样做时,我希望应用程序弄清楚它是哪种类型,并相应地仅显示适用的列。这是一个示例应用程序,用于说明该应用程序。

我使用的两个 Model 类:

public class LogDataSet
{
    public List<LogData> LogData { get; set; }
    public List<bool> DisplayColumns { get; set; }
}

public class LogData
{
    public int LineNum { get; set; }
    public string Data1 { get; set; }
    public string Data2 { get; set; }
    public string Data3 { get; set; }
}

DisplayColumsn 列表本质上告诉您应该显示哪些列。通过查看下面的代码应该很容易理解其余部分。我有两个示例 Button 事件来模拟用户选择不同的日志类型。在阅读的时候,我也阅读了正确的sys文件类型,并显示数据。

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private LogDataSet _logDataSet;
    public LogDataSet LogDataSet
    {
        get { return _logDataSet; }
        set { _logDataSet = value; OnPropertyChanged("LogDataSet"); }
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void BtnType1_Click(object sender, RoutedEventArgs e)
    {
        var type1logs = new List<LogData>
        {
            new LogData() { LineNum = 1, Data1 = "Data1A", Data2 = "Data2A" },
            new LogData() { LineNum = 2, Data1 = "Data1B", Data2 = "Data2B" },
            new LogData() { LineNum = 3, Data1 = "Data1C", Data2 = "Data2C" }
        };
        LogDataSet = new LogDataSet() { LogData = type1logs };
        LogDataSet.DisplayColumns = ReadSysFiles("Type1.sys");

    }

    private void BtnType2_Click(object sender, RoutedEventArgs e)
    {
        var type2logs = new List<LogData>
        {
            new LogData() { LineNum = 1, Data1 = "Data1A", Data3 = "Data3A" },
            new LogData() { LineNum = 2, Data1 = "Data1B", Data3 = "Data3B" },
            new LogData() { LineNum = 3, Data1 = "Data1C", Data3 = "Data3C" }
        };
        LogDataSet = new LogDataSet() { LogData = type2logs };
        LogDataSet.DisplayColumns = ReadSysFiles("Type2.sys");
    }

    public static List<bool> ReadSysFiles(string path)
    {
        var data = File.ReadAllLines(path).ToList()[0];
        var parts = data.Split(',');
        var display = new List<bool>();
        foreach (var item in parts)
        {
            display.Add(item == "1" ? true : false);
        }
        return display;
    }
}

还有我的简单XAML

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <DataGrid Grid.Row="0" Grid.ColumnSpan="2" 
              Name="DgLogs" Margin="8" AutoGenerateColumns="False" IsReadOnly="True"
              ItemsSource="{Binding LogDataSet.LogData}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Line #" Binding="{Binding LineNum}"/>
            <DataGridTextColumn Header="Data1" Binding="{Binding Data1}"/>
            <DataGridTextColumn Header="Data2" Binding="{Binding Data2}"/>
            <DataGridTextColumn Header="Data3" Binding="{Binding Data3}"/>
        </DataGrid.Columns>
    </DataGrid>
    <Button x:Name="BtnType1" Content="Type 1" 
            Width="100" Height="28" Margin="4, 4, 4, 8" Grid.Column="0" Grid.Row="1"
            Click="BtnType1_Click"/>
    <Button x:Name="BtnType2" Content="Type 2" 
            Width="100" Height="28" Margin="4, 4, 4, 8" Grid.Column="1" Grid.Row="1"
            Click="BtnType2_Click"/>
</Grid>

这显然会显示所有 4 列,列中的空单元格不包含每种文件类型的数据。但是,根据我的bool 列表DisplayColumns,我将如何隐藏这些列?我看到很多 SO 帖子显示如何基于另一个控件等执行此类操作,但没有显示如何绑定到列表的索引。

编辑

我也愿意接受其他创造性的解决方案,而且我不必阅读sys 文件并使用DisplayColumns 列表。这是我采取的方法,但只要完成工作,我就可以走另一条路。

【问题讨论】:

    标签: c# wpf data-binding datagrid show-hide


    【解决方案1】:

    您可以为视图中的LogDataSet 属性处理PropertyChanged 事件:

    public MainWindow()
    {
        InitializeComponent();
        PropertyChanged += MainWindow_PropertyChanged;
        DataContext = this;
    }
    
    private void MainWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(LogDataSet))
        {
            for (int i = 0; i < DgLogs.Columns.Count; i++)
            {
                DgLogs.Columns[i].Visibility = LogDataSet.DisplayColumns[i] ? Visibility.Visible : Visibility.Collapsed;
            }
        }
    }
    

    只需确保在设置 LogDataSet 属性时初始化 DisplayColumns 属性,即在初始化程序中设置它而不是之后:

    private void BtnType1_Click(object sender, RoutedEventArgs e)
    {
        var type1logs = new List<LogData>
        {
            new LogData() { LineNum = 1, Data1 = "Data1A", Data2 = "Data2A" },
            new LogData() { LineNum = 2, Data1 = "Data1B", Data2 = "Data2B" },
            new LogData() { LineNum = 3, Data1 = "Data1C", Data2 = "Data2C" }
        };
        LogDataSet = new LogDataSet() { LogData = type1logs, DisplayColumns = ReadSysFiles("Type1.sys") };
    }
    
    private void BtnType2_Click(object sender, RoutedEventArgs e)
    {
        var type2logs = new List<LogData>
        {
            new LogData() { LineNum = 1, Data1 = "Data1A", Data3 = "Data3A" },
            new LogData() { LineNum = 2, Data1 = "Data1B", Data3 = "Data3B" },
            new LogData() { LineNum = 3, Data1 = "Data1C", Data3 = "Data3C" }
        };
        LogDataSet = new LogDataSet() { LogData = type2logs, DisplayColumns = ReadSysFiles("Type2.sys") };
    }
    

    【讨论】:

    • 很遗憾我忘了提到我在我的应用程序中使用了MVVM 模式,所以我宁愿不从我的ViewModel 访问数据网格,我也不想这样做隐藏在我的View 中的列(无论如何,我无法访问我的View 中的LogDataSet)。
    • 错了。如果您使用 MVVM,您应该隐藏视图中的列。其他一切都会打破这种模式。视图模型对视图中的任何列一无所知。它公开了DisplayColumns 集合,但隐藏列取决于视图。所以你应该在视图中处理PropertyChanged 事件。
    【解决方案2】:

    如果您只想隐藏这些列,您可以在网格初始化后轻松完成。

    <DataGrid Name="DgLogs" Initialized="DgLogs_OnInitialized">
    

    您有以下两种方法可以做到这一点:

    private void DgLogs_OnInitialized(object sender, EventArgs e)
    {
        if (!(sender is DataGrid grid)) return;
    
        //Column with index 1 will be hidden.
        grid.Columns[1].Visibility = Visibility.Hidden;
    
        //Column with header = Data2 will be hidden
        var column = grid.Columns.FirstOrDefault(c => c.Header.Equals("Data2"));
        column.Visibility = Visibility.Hidden;
    }
    

    编辑

    您还可以使用绑定并使用转换器来查找要隐藏的列:

        <DataGrid Name="DgLogs" Margin="8" AutoGenerateColumns="False" IsReadOnly="True" Initialized="DgLogs_OnInitialized">
            <DataGrid.Resources>
                <myProject:MyHeaderToVisibilityConverter x:Key="MyHeaderToVisibilityConverter"/>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Line #" Binding="{Binding LineNum}" Visibility="{Binding Path=Header, RelativeSource={RelativeSource Self},  Converter={StaticResource MyHeaderToVisibilityConverter}}"/>
                <DataGridTextColumn Header="Data1" Binding="{Binding Data1}" Visibility="{Binding Path=Header, RelativeSource={RelativeSource Self},  Converter={StaticResource MyHeaderToVisibilityConverter}}"/>
                <DataGridTextColumn Header="Data2" Binding="{Binding Data2}" Visibility="{Binding Path=Header, RelativeSource={RelativeSource Self},  Converter={StaticResource MyHeaderToVisibilityConverter}}"/>
                <DataGridTextColumn Header="Data3" Binding="{Binding Data3}" Visibility="{Binding Path=Header, RelativeSource={RelativeSource Self},  Converter={StaticResource MyHeaderToVisibilityConverter}}"/>
            </DataGrid.Columns>
        </DataGrid>
    

    转换器:

    public class MyHeaderToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //Find your settings for each header here.
            //and compare below with the results
            switch (value?.ToString())
            {
                case "Line #":
                    return Visibility.Visible;
                case "Data1":
                    return Visibility.Collapsed;
                default:
                    return Visibility.Visible;
            }
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    【讨论】:

    • 这并不能完全解决我的问题。正如我所说,有不同类型的文件,只有当用户选择文件时我才会知道类型。我想使用我拥有的bool 列表,或者一个有助于不进行硬编码的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-18
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多