简体   繁体   中英

WPF databinding a user control

Ive been looking for hours on a solution to this. I have a tab Adapter class that im using to fill a tab control

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

The point of this is to decide what type of file is being added and load up the correct user control inside of it to display that file.

my main window xaml for the tab control is

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

the header works, and if i changed

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

into

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

Then it all binds correctly, I have a feeling it has something to do with the data context on my tab adapter. but not exactly sure what it needs to be set to.

my xaml for the tab adapter is

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

it will all compile and run, but only the constructor of the user control gets hit, and at that only once no matter how many tabs I create. This is my first WPF application so my apologies if it is something stupid im missing. (or if my methodology of setting up a adapter of sorts is not the best way of solving this issue).

Do not set the DataContext of a UserControl from within the UserControl itself. It defeats the whole point of having "lookless" controls that can be used to display whatever you pass it.

Your fileName binding is failing because the DataContext = this , and this is the TabAdapter control itself, and TabAdapter.fileName is not actually set to anything. (Remember, binding to tell a property to retrieve it's value somewhere else is different from setting the value directly)

As for the constructor not running more than once, that is by design. Since you told the TabControl to use the TabAdapter control as a template, it will create one copy of the TabAdapter , and simply replace the .DataContext behind the control whenever you switch tabs. This increases performance as it doesn't have to keep initializing and tracking a separate control for each tab, and reduces the memory used.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM