Frage

I have a MainWindow:Window class which holds all the data in my program. The MainWindow's .xaml contains only an empty TabControl which is dynamically filled (in the code-behind).

One of the tabs (OptionsTab) has its .DataContext defined as the MainWindow, granting it access to all of the data. The OptionsTab has a DataGrid which has a column populated with Buttons, as shown below:

enter image description here

The DataGrid is populated with DataGridTemplateColumns, where the DataTemplate is defined in the main <Grid.Resources>. I would like to bind this button to a function in the MainWindow (not the OptionsTab in which it resides).

When the OptionsTab is created, it's .DataContext is set as the MainWindow, so I would have expected that defining the DataTemplate as below would have done it.

<DataTemplate x:Key="DeadLoadIDColumn">
    <Button Content="{Binding Phases, Path=DeadLoadID}" Click="{Binding OpenDeadLoadSelector}"/>
</DataTemplate>

I thought this would mean the Click event would be bound to the desired OptionsTab.DataContext = MainWindow's function.This, however, didn't work (the Content did, however). So then I started looking things up and saw this answer to another SO question (by Rachel, who's blog has been of great help for me), from which I understood that you can't {bind} the click event to a method, but must instead bind the Command property to an ICommand property (using the helper RelayCommand class) which throws you into the desired method. So I implemented that, but it didn't work. If I place a debug breakpoint at the DeadClick getter or on OpenDeadLoadSelector() and run the program, clicking on the button doesn't trigger anything, meaning the {Binding} didn't work.

I would like to know if this was a misunderstanding on my part or if I simply did something wrong in my implementation of the code, which follows (unrelated code removed):

MainWindow.xaml

<Window x:Class="WPF.MainWindow"
    x:Name="Main"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF" SizeToContent="WidthAndHeight">
<TabControl Name="tabControl"
            SelectedIndex="1"
            ItemsSource="{Binding Tabs, ElementName=Main}">
</TabControl>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    ICommand deadClick;
    public ICommand DeadClick
    {
        get
        {
            if (null == deadClick)
                deadClick = new RelayCommand(p => OpenDeadLoadSelector());
            return deadClick;
        }
    }
    public ObservableCollection<TabItem> Tabs = new ObservableCollection<TabItem>();
    public static DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow));
    public ICollectionView ITabsCollection
    {
        get { return (ICollectionView)GetValue(TabsProperty); }
        set { SetValue(TabsProperty, value); }
    }
    public ObservableCollection<NPhase> Phases = new ObservableCollection<NPhase>();
    public static DependencyProperty PhasesProperty = DependencyProperty.Register("Phases", typeof(ICollectionView), typeof(MainWindow));
    public ICollectionView IPhasesCollection
    {
        get { return (ICollectionView)GetValue(PhasesProperty); }
        set { SetValue(PhasesProperty, value); }
    }
    public ObservableCollection<string> Loads = new ObservableCollection<string>();
    public static DependencyProperty LoadsProperty = DependencyProperty.Register("Loads", typeof(ICollectionView), typeof(MainWindow));
    public ICollectionView ILoadsCollection
    {
        get { return (ICollectionView)GetValue(LoadsProperty); }
        set { SetValue(LoadsProperty, value); }
    }
    
    void OpenDeadLoadSelector()
    {
        int a = 1;
    }
    public MainWindow()
    {
        var optionsTab = new TabItem();
        optionsTab.Content = new NOptionsTab(this);
        optionsTab.Header = (new TextBlock().Text = "Options");
        Tabs.Add(optionsTab);
        ITabsCollection = CollectionViewSource.GetDefaultView(Tabs);

        Loads.Add("AS");
        Loads.Add("2");

        InitializeComponent();
    }
}

OptionsTab.xaml

<UserControl x:Class="WPF.NOptionsTab"
         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" 
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
         mc:Ignorable="d" 
         xmlns:l="clr-namespace:WPF">
<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="DeadLoadIDColumn">
            <Button Content="{Binding Phases, Path=DeadLoadID}" Command="{Binding Path=DeadClick}"/>
        </DataTemplate>
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <!-- ... -->
    </Grid.RowDefinitions>
    <Grid Grid.Row="0">
        <!-- ... -->
    </Grid>
    <Grid Grid.Row="1">
        <!-- ... -->
    </Grid>
    <l:NDataGrid Grid.Row="2"
                 x:Name="PhaseGrid"
                 AutoGenerateColumns="False">
        <l:NDataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
            <DataGridTextColumn Header="Date (days)" Binding="{Binding Path=Date}"/>
            <DataGridTemplateColumn Header="Deadload" CellTemplate="{StaticResource DeadLoadIDColumn}"/>
        </l:NDataGrid.Columns>
    </l:NDataGrid>
</Grid>
</UserControl>

OptionsTab.xaml.cs

public NOptionsTab(MainWindow w)
{
    DataContext = w;
    InitializeComponent();
    PhaseGrid.ItemsSource = w.Phases;
}

While we're at it (and this might be a related question), why does {Binding Phases, Path=DeadLoadID} work on the DataTemplate (which is why the buttons appear with "Select"), but if I do {Binding Phases, Path=Name} in the PhaseGrid and remove the .ItemsSource code from the constructor, nothing happens? Shouldn't the PhaseGrid inherit its parent's (NOptionsTab / Grid) DataContext? Hell, even setting PhaseGrid.DataContext = w; doesn't do anything without the .ItemsSource code.

EDIT (27/04/14):

I think that knowing the contents of the NPhase class itself will be of use, so here it is:

public class NPhase : INotifyPropertyChanged
{
    string name;
    double date;
    string deadLoadID = "Select";
    public event PropertyChangedEventHandler PropertyChanged;
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            EmitPropertyChanged("Name");
        }
    }
    public double Date
    {
        get { return date; }
        set
        {
            date = value;
            EmitPropertyChanged("Date");
        }
    }
    public string DeadLoadID
    {
        get { return deadLoadID; }
        set
        {
            deadLoadID = value;
            EmitPropertyChanged("DeadLoadID");
        }
    }
    void EmitPropertyChanged(string property)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(property));
    }
    public NPhase(double _date, string _name)
    {
        date = _date;
        name = _name;
    }
}

EDIT (29/04/14):

A simplified project (getting rid of everything that wasn't necessary) can be downloaded from here (https://dl.dropboxusercontent.com/u/3087637/WPF.zip)

War es hilfreich?

Lösung

I think that there is the problem that you do not specify data source properly for the data item inside your grid.

I think that the data source for your button column is NPhase instance. So it has no DeadClick property. So, you can check it using Output window in Visual Studio.

I suggest that you can do something like that:

<DataTemplate x:Key="DeadLoadIDColumn">
    <Button Content="{Binding Phases, Path=DeadLoadID}"
            Command="{Binding Path=DataContext.DeadClick, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type l:NDataGrid}}}"/>
</DataTemplate>

I currently do not understand how you can compile Content="{Binding Phases, Path=DeadLoadID}", because as I thought the default value for Binding clause is the Path property, and you have specified it twice.

EDIT After I got the small solution all becomes clear. Here is the modified solution. All what I changed in it - I have added RelativeSource to the command binding as I described above, and I added MainWindow as DataContext for your OptionsTab (you have specified it in the question, but not in the project). That's it - all works fine - the command getter is called, and the command is executed when you click the button.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top