【问题标题】:WPF Button Command bindingWPF 按钮命令绑定
【发布时间】:2014-04-26 16:36:56
【问题描述】:

我有一个MainWindow:Window 类,它保存了我程序中的所有数据。 MainWindow 的 .xaml 仅包含一个空的 TabControl,它是动态填充的(在代码隐藏中)。

其中一个选项卡 (OptionsTab) 将其 .DataContext 定义为 MainWindow,授予其访问所有数据的权限。 OptionsTab 有一个DataGrid,其中有一列填充有Buttons,如下所示:

DataGrid 填充有DataGridTemplateColumns,其中DataTemplate 在主<Grid.Resources> 中定义。我想将此按钮绑定到MainWindow(而不是它所在的OptionsTab)中的一个函数。

当创建OptionsTab 时,它的.DataContext 被设置为MainWindow,所以我希望像下面这样定义DataTemplate 就可以了。

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

我认为这意味着 Click 事件将绑定到所需的 OptionsTab.DataContext = MainWindow's 函数。但是,这不起作用(但是 Content 起作用了)。于是我开始查找并看到this answer to another SO question(Rachel 的博客对我有很大帮助),从中我了解到您不能将点击事件{绑定}到方法,而是必须绑定将Command 属性转换为ICommand 属性(使用帮助器RelayCommand 类),这会将您带入所需的方法。所以我实现了它,但它没有用。如果我在DeadClick getter 或OpenDeadLoadSelector() 上放置调试断点并运行程序,单击按钮不会触发任何操作,这意味着 {Binding} 不起作用。

我想知道这是否是我的误解,或者我只是在我的代码实现中做错了什么(删除了不相关的代码):

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

虽然我们正在处理它(这可能是一个相关的问题),但为什么 {Binding Phases, Path=DeadLoadID} 可以在 DataTemplate 上工作(这就是按钮出现“选择”的原因),但如果我这样做 {Binding Phases, Path=Name}在 PhaseGrid 中并从构造函数中删除 .ItemsSource 代码,没有任何反应? PhaseGrid 不应该继承其父级的(NOptionsTab / Grid)DataContext 吗?见鬼,没有.ItemsSource 代码,即使设置PhaseGrid.DataContext = w; 也无济于事。

编辑(27/04/14):

我认为知道NPhase类本身的内容会有用,所以这里是:

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

编辑(29/04/14):

可以从这里 (https://dl.dropboxusercontent.com/u/3087637/WPF.zip) 下载一个简化的项目(去掉所有不必要的东西)

【问题讨论】:

    标签: c# wpf xaml binding icommand


    【解决方案1】:

    我认为存在您没有为网格内的数据项正确指定数据源的问题。

    我认为您的按钮列的数据源是 NPhase 实例。所以它没有 DeadClick 属性。因此,您可以在 Visual Studio 中使用Output 窗口进行检查。

    我建议你可以这样做:

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

    我目前不明白如何编译 Content="{Binding Phases, Path=DeadLoadID}",因为我认为 Binding 子句的默认值是Path 属性,并且您已经指定了两次。

    编辑 在我得到小解决方案后,一切都变得清晰起来。 Here 是修改后的解决方案。我在其中所做的所有更改-如上所述,我已将 RelativeSource 添加到命令绑定中,并将 MainWindow 添加为 OptionsTab 的 DataContext (您已在问题中指定它,但未在项目中指定)。就是这样 - 一切正常 - 调用命令 getter,并在单击按钮时执行命令。

    【讨论】:

    • Phases 是一个ObservableCollection&lt;NPhase&gt; 属性,因此对于列,我必须将其绑定到此集合,然后对于每个单元格(或行),将其绑定到NPhase.DeadLoadID 属性。 {Binding Phases.DeadLoadID 不起作用,因为 .DeadLoadID 不是集合的属性,而是集合的各个元素的属性。我已经用 NPhase 的代码编辑了 OP,这可能有助于理清思路。
    • 你能告诉我DataContext 对应的Button 的类型吗?
    • 除了奇怪的 Binding MSDN:“Binding 标记扩展使用 Binding.Path 作为概念上的“默认属性”,其中 Path= 不需要出现在表达式中。”
    • 该按钮没有定义DataContext,因此从其父级继承它,该父级以MainWindow 作为其DataContext(请参阅OptionsTab 构造函数)。而且我不知道我还能如何定义绑定,因为标准 {Binding Phases.DeadLoadID} 不会(也不会)工作,因为 Phases 不包含属性 .DeadLoadID,只有 Phases[i] 有。此外,这里的主要问题是关于将 Command 属性绑定到 ICommand,我似乎无法正常工作。
    • 您是否尝试过使用 RelativeSource 属性的解决方案?
    猜你喜欢
    • 2013-09-16
    • 2015-06-07
    • 1970-01-01
    • 1970-01-01
    • 2013-10-09
    • 2017-01-31
    • 2015-12-09
    • 1970-01-01
    • 2011-02-17
    相关资源
    最近更新 更多