WPF 数据绑定用户控件

WPF databinding a user control

我花了好几个小时寻找解决方案。 我有一个选项卡适配器 class,我用它来填充选项卡控件

public partial class TabAdapter : UserControl
{
    public static readonly DependencyProperty fileNameProperty =
        DependencyProperty.Register(
            "fileName",
            typeof(string),
            typeof(TabAdapter),
            new FrameworkPropertyMetadata(
                string.Empty,
                FrameworkPropertyMetadataOptions.AffectsRender,
                new PropertyChangedCallback(OnFileNamePropertyChanged),
                new CoerceValueCallback(coerceFileName)
                ),
            new ValidateValueCallback(fileNameValidationCallback)
        );

    public TabAdapter()
    {
        InitializeComponent();
        //initializeInterior();
        CreateSaveCommand();
        TabAdapterContent.DataContext = this;
        Console.WriteLine("constructor hit.");
    }

    public string fileName
    {
        get { return (string)GetValue(fileNameProperty); }
        set { SetValue(fileNameProperty, value); }
    }

    private ColumnMapper _columnMap;

    private TableMapper _tableMap;

    private TabType tabType;

    private enum TabType { TABLE_MAPPER, COLUMN_MAPPER, ERROR_MSG }

    private static object coerceFileName(DependencyObject d, object value)
    {
        return fileName;
    }

    private static bool fileNameValidationCallback(object Value)
    {
        string fn = (string)Value;
        if (fn.Equals(string.Empty))
        {
            return true;
        }
        FileInfo fi = new FileInfo(fn);
        return ((fi.Exists && fi.Extension.Equals(".csv")));
    }

    private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        TabAdapter source = d as TabAdapter;
        Console.WriteLine("got to property changer: " + (string)args.NewValue + " :new / old: " + (string)args.OldValue);
        source.initializeInterior();
    }

    private void initializeInterior()
    {
        Console.WriteLine("initializing Interior filename: " + fileName);
        if (Regex.IsMatch(fileName, @".*_SourceTableMapping.csv$"))
        {
            tabType = TabType.TABLE_MAPPER;
            _tableMap = new TableMapper(fileName);
            Grid.SetRow(_tableMap, 0);
            Grid.SetColumn(_tableMap, 0);
            //clear out the content.
            this.TabAdapterContent.Children.Clear();
            //add new content
            this.TabAdapterContent.Children.Add(_tableMap);
        }
        else if (fileName.EndsWith(".csv"))
        {
            tabType = TabType.TABLE_MAPPER;
            _columnMap = new ColumnMapper(fileName);
            Grid.SetRow(_columnMap, 0);
            Grid.SetColumn(_columnMap, 0);
            //clear out the content.
            this.TabAdapterContent.Children.Clear();
            //add new content
            this.TabAdapterContent.Children.Add(_columnMap);
        }
        else
        {
            tabType = TabType.ERROR_MSG;
            TextBlock tb = new TextBlock();
            tb.Text = "The File: " + fileName + " is not a valid mapping file.";
            Grid.SetRow(tb, 0);
            Grid.SetColumn(tb, 0);
            //clear out the content.
            this.TabAdapterContent.Children.Clear();
            //add new content
            this.TabAdapterContent.Children.Add(tb);
        }
    }
}

这样做的目的是决定要添加什么类型的文件并在其中加载正确的用户控件以显示该文件。

我的主要 window xaml 选项卡控件是

<TabControl x:Name="tabControl" Grid.Column="1" Grid.Row="0" ItemsSource="{Binding openFileNames, Mode=OneWay}">
            <TabControl.LayoutTransform>
                <!-- Allows to zoom the control's content using the slider -->
                <ScaleTransform CenterX="0"
                     CenterY="0" />
                <!-- part of scale transform ScaleX="{Binding ElementName=uiScaleSlider,Path=Value}"
                     ScaleY="{Binding ElementName=uiScaleSlider,Path=Value}" />-->
            </TabControl.LayoutTransform>
            <!-- Header -->
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}" />
                </DataTemplate>
            </TabControl.ItemTemplate>
            <!-- Content -->
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <local:TabAdapter fileName="{Binding fileName}" />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

header 有效,如果我改变了

<local:TabAdapter fileName="{Binding fileName}" />

进入

<TextBlock Text="{Binding fileName}" />

然后它全部正确绑定,我感觉它与我的选项卡适配器上的数据上下文有关。但不确定需要设置什么。

我的 xaml 选项卡适配器是

<UserControl x:Class="ImportMappingGui.TabAdapter"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         mc:Ignorable="d"
         d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="TabAdapterContent">
    <Grid.RowDefinitions>
        <RowDefinition Height = "*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
</Grid>

它会全部编译并且运行,但只有用户控件的构造函数被命中,而且无论我创建多少选项卡都只会命中一次。 这是我的第一个 WPF 应用程序,如果我遗漏了一些愚蠢的东西,我深表歉意。 (或者如果我设置某种适配器的方法不是解决此问题的最佳方法)。

不要在 UserControl 本身中设置 UserControlDataContext。它破坏了拥有 "lookless" 控件的全部意义,这些控件可用于显示您传递给它的任何内容。

您的 fileName 绑定失败,因为 DataContext = thisthisTabAdapter 控件本身,而 TabAdapter.fileName 实际上没有设置任何内容. (请记住,绑定告诉 属性 在其他地方检索它的值与直接设置值不同)

至于构造函数不运行不止一次,那是设计使然。由于您告诉 TabControl 使用 TabAdapter 控件作为模板,它会创建一份 TabAdapter,并在您切换选项卡时简单地替换控件后面的 .DataContext。这提高了性能,因为它不必为每个选项卡持续初始化和跟踪单独的控件,并减少了使用的内存。