WPF选项卡控件更改SelectedIndex/SelectedItem会导致绑定错误



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

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

我正在努力解决的问题是在没有错误的情况下操纵TabControl。每当我在将选项卡设置为最后一个选项卡后关闭该选项卡时,我都会收到一条毫无帮助的错误消息:错误4.无效的TabStripPlacement;选项卡项目无目标;对象找不到源:RelativeSource FindAncestor,AncestorType='System.Windows.Controls.TabControl',AncetorLevel='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();
}
}

主窗口中的C#代码.xaml.cs:

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;
}
}
}    

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

修复方法可能很简单,只需从TabCloseButton_Click中更新SelectedIndex属性的值,但请在删除选项卡项之前立即更新。如果SelectedIndex的值小于要删除的选项卡的索引,则无需执行此操作(因为SelectedIndex在删除后仍然有效(。

最新更新