【问题标题】:WPF TabControl 更改 SelectedIndex / SelectedItem 导致绑定错误
【发布时间】:2022-01-23 14:04:18
【问题描述】:

我已经尝试解决这个问题大约一个星期了,但找不到任何有助于解决这个问题的方法。 我在 WPF 采用的数据绑定方法方面相当缺乏经验(与 Unity、WinForms 等相比),希望这里有人能提供帮助。

我正在开发 C# 中的 WPF 应用程序,以便能够在 DataGrid 中显示 *.csv 文件并将一个文件与另一个文件进行比较(因此我可能需要 3 个 TabControl:其中 2 个加载到 *.csv 和最后一个在 3 个选项卡中显示结果(匹配、差异 A -> B 和差异 (B -> A))。

我正在努力解决的问题是无错误地操作 TabControl。 每当我在将选项卡设置为最后一个选项卡后关闭选项卡时,都会收到一条非常无用的错误消息: 错误; 4;空值; TabStripPlacement; TabItem.NoTarget;目的;找不到源:RelativeSource FindAncestor,AncestorType='System.Windows.Controls.TabControl',AncestorLevel='1'。

我注意到的是,如果我没有设置 SelectedIndex 并删除一个选项卡,一切都很好(尽管不是我想要的)。

通过将 *.csv 文件拖放到 tabControl 中,然后用“x”关闭它,可以不断地重新创建错误。

XAML:

<Window x:Class="CsvComparer.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:CsvComparer"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:TabViewModel xmlns="clr-namespace:CsvComparer"/>
</Window.DataContext>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="20"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    
    <TabControl Grid.Column="1" Grid.Row="1"
                x:Name="firstFileTabControl"         
                ItemsSource="{Binding Tabs}"
                SelectedIndex="{Binding SelectedIndex}"
                TabStripPlacement="Top"
                AllowDrop="True"
                Drop="FileTabControl_OnDrop"                    
                Tag="0">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Header}"/>
                    <Button Content="X" Click="TabCloseButton_Click" Tag="{Binding InstanceId}"/>
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <DataGrid ItemsSource="{Binding DataTable}" 
                          CanUserAddRows="False"
                          CanUserDeleteRows="False"
                          EnableColumnVirtualization="True"
                          CanUserResizeRows="False"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

模型:

public sealed class TabItemModel
{
    public string Header { get; set; }
    public DataTable DataTable { get; set; }        
    public long InstanceId { get; set; }
}

public sealed class TabViewModel : INotifyPropertyChanged
{       
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<TabItemModel> Tabs { get; set; }

    private int selectedIndex = 0;

    public int SelectedIndex
    {
        get
        {
            return selectedIndex;
        }

        set
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedIndex)));
            selectedIndex = value;
        }
    }

    public TabViewModel()
    {
        Tabs = new();
    }
}

MainWindow.xaml.cs 中的 C# 代码:

using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CsvComparer
{
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new TabViewModel();
    }

    #region EventHandlers

    private long instanceCount = 0;
    private void FileTabControl_OnDrop(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            string[] fileNames = (string[])e.Data.GetData(DataFormats.FileDrop);
            string file = fileNames.FirstOrDefault();

            TabItemModel newTab = new()
            {
                InstanceId = instanceCount++,
                Header = $"{ System.IO.Path.GetFileName(file) }",
                DataTable = GetCsvRecords(file),                    
            };

            var model = (TabViewModel)DataContext;
            model.Tabs.Add(newTab);
            
            // when commenting out this line, no errors
            model.SelectedIndex++;                                
        }
    }

    private void TabCloseButton_Click(object sender, RoutedEventArgs e)
    {
        Button button = (Button)sender;
        long id = (long)button.Tag;

        var model = (TabViewModel)DataContext;
        var item = model.Tabs.FirstOrDefault(x => x.InstanceId == id);
        if (item != null)
        {
            model.Tabs.Remove(item);
        }            
    }

    #endregion

    private CsvConfiguration CreateCsvConfig()
    {
        CsvConfiguration output = new CsvConfiguration(System.Globalization.CultureInfo.CurrentCulture)
        {
            HasHeaderRecord = true,
            // Delimiter = ", ",
            BadDataFound = new BadDataFound((data) => Debug.WriteLine($"Found faulty data: '{ data.RawRecord }'")),
            MissingFieldFound = new MissingFieldFound((data) => Debug.WriteLine($"Missing field found at Row '{ data.Index }', Header '{ data.HeaderNames }'")),
            ShouldSkipRecord = record => record.Record.All(string.IsNullOrWhiteSpace),
        };

        return output;
    }

    private DataTable GetCsvRecords(string path, CsvConfiguration config = null)
    {
        DataTable output = new();
        config ??= CreateCsvConfig();

        using (StreamReader sr = new(path))
        {
            using (CsvReader csvReader = new(sr, config))
            {
                using (CsvDataReader dataReader = new(csvReader))
                {
                    output.Load(dataReader);
                }
            }
        }

        return output;
    }
}

}    

【问题讨论】:

    标签: c# wpf binding tabcontrol


    【解决方案1】:

    这是我对正在发生的事情的预感:假设您打开了三个选项卡并选择了最后一个选项卡 (SelectedIndex=2)。关闭选项卡后,可观察集合将仅包含两个选项卡项,因此您会收到绑定错误,因为 SelectedIndex (2) 的值现在无效。

    修复可能就像从 TabClos​​eButton_Click 中更新 SelectedIndex 属性的值一样简单,但在删除选项卡项之前执行此操作。如果 SelectedIndex 的值小于要删除的选项卡的索引,则不需要这样做(因为 SelectedIndex 在删除后仍然有效)。

    【讨论】:

      猜你喜欢
      • 2021-10-27
      • 2012-08-23
      • 1970-01-01
      • 2013-06-15
      • 1970-01-01
      • 1970-01-01
      • 2011-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多