【发布时间】: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