简体   繁体   English

WPF将控件绑定到另一个控件

[英]WPF Binding control to another control

I have the following WPF window. 我有以下WPF窗口。 Changing the value of #Cables adds (or removes) tabs to the TabControl (C2, C3, C4...). 更改#Cables的值会向TabControl (C2,C3,C4 ...)添加(或删除)标签。 Changing #Phases adds new rows to the DataGrid . 更改#Phase会向DataGrid添加新行。

窗口

All of the tabs (other than this Options one) have the same format, so I created a UserControl NTab class which has its own .xaml and code-behind. 所有选项卡(除了“选项”之外)均具有相同的格式,因此我创建了一个UserControl NTab类,该类具有自己的.xaml和代码后置。

Now, each of the other tabs will have a ComboBox , where the user selects the appropriate Phase (by the name property). 现在,每个其他选项卡将具有一个ComboBox ,用户可以在其中选择适当的“阶段”(通过name属性)。 For that to be possible, the NTab needs to be aware of the Phase DatGrid in the Options tab. 为此, NTab需要在“选项”选项卡中了解Phase DatGrid My code is currently split into two classes: 我的代码当前分为两类:

  • MainWindow , which contains the code (.xaml and code-behind) for the window as a whole and for the Options Tab; MainWindow ,包含整个窗口和“选项”选项卡的代码(.xaml及其后置代码);
  • NTab , which contains the code (.xaml and code-behind) for itself. NTab ,其NTab包含代码(.xaml和背后代码)。

The Phase DataGrid's ItemSource is an ObservableCollection , so what I've done is sent the collection to the NTab constructor and tied its .CollectionChanged event to the following NTab function (Phases is an ObservableCollection<string> dependency property): 该阶段DataGrid's ItemSourceObservableCollection ,我这样做有什么被送到收集到NTab构造和它并列.CollectionChanged事件以下NTab功能(阶段是一个ObservableCollection<string>依赖属性):

public void PhasesChanged(object source, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    var pC = source as ObservableCollection<NPhase>;
    Phases.Clear();
    for (int i = 0; i < pC.Count; i++)
        Phases.Add("" + (i + 1));
}

This creates a copy of the DataGrid's data (just the names) and stores it inside each NTab as a dependency property bound to the ComboBox . 这将创建DataGrid数据的副本(只是名称),并将其作为绑定到ComboBox的依赖项属性存储在每个NTab中。 This works. 这可行。

My question is if there is a better way to do this. 我的问题是,是否有更好的方法可以做到这一点。 I would rather not have to create "redundant" copies of data and would like to be able to bind the ComboBoxes directly to the DataGrid . 我宁愿不必创建数据的“冗余”副本,也希望能够将ComboBoxes直接绑定到DataGrid Is this possible? 这可能吗?


EDIT: Adding my code. 编辑:添加我的代码。 I've deleted parts of the .xaml and code-behind which weren't relevant to the question. 我已删除了与该问题无关的.xaml和代码隐藏部分。

MainWindow.xaml 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"
    xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
    xmlns:l="clr-namespace:WPF"
    Title="WPF" SizeToContent="WidthAndHeight">
<TabControl Name="tabControl" SelectedIndex="1">
    <TabItem Name="Options">
        <TabItem.Header>
            <TextBlock>Options</TextBlock>
        </TabItem.Header>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <!-- ... -->
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <!-- ... -->
                </Grid.ColumnDefinitions>
                <Label Grid.Column="0"># Cables</Label>
                <xctk:IntegerUpDown Name="nCables" 
                                    Grid.Column="1" 
                                    Minimum="1" 
                                    Value="1"
                                    ValueChanged="nCablesChanged"/>
            </Grid>
            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <!-- ... -->
                </Grid.ColumnDefinitions>
                <Label Grid.Column="0"># Phases</Label>
                <xctk:IntegerUpDown Name="nPhases" 
                                    Grid.Column="1" 
                                    Minimum="1" 
                                    Value="4"
                                    ValueChanged="nPhasesChanged"/>
            </Grid>
            <l:NDataGrid Grid.Row="2"
                         x:Name="PhaseGrid"
                         ItemsSource="{Binding Phases, ElementName=Main}" 
                         LoadingRow="RowIndex"
                         CanUserAddRows="False"
                         CanUserDeleteRows="False"
                         CanUserReorderColumns="False"
                         CanUserSortColumns="False"
                         VerticalScrollBarVisibility="Hidden"
                         Block.TextAlignment="Center"/>
        </Grid>
    </TabItem>
</TabControl>
</Window>

MainWindow.xaml.cs MainWindow.xaml.cs

public partial class MainWindow : Window
{
    ObservableCollection<NPhase> Phases;
    public static DependencyProperty PhasesProperty = DependencyProperty.Register("Phases", typeof(ICollectionView), typeof(MainWindow));
    public ICollectionView IPhasesCollection
    {
        get { return (ICollectionView)GetValue(PhasesProperty); }
        set { SetValue(PhasesProperty, value); }
    }
    /// <summary>Controls the number of cables to be created</summary>
    /// <param name="sender">(IntegerUpDown)nCables</param>
    /// <param name="e">not used</param>
    private void nCablesChanged(object sender, RoutedEventArgs e)
    {
        int n = tabControl.Items.Count - 1;
        var o = sender as IntegerUpDown;
        int v = (int)o.Value;
        if (v > n)
        {
            for (int i = n; i < v; i++)
            {
                TabItem tab = new TabItem();
                tab.Header = "C" + (i + 1);
                tab.Content = new NTab(Phases);
                tabControl.Items.Add(tab);
            }
        }
        else if (v < n)
        {
            for (int i = v; i < n; i++)
                tabControl.Items.RemoveAt(n);
        }
    }
    /// <summary>Modifies the DataGrid according to the number of phases desired</summary>
    /// <param name="sender">(IntegerUpDown)nPhases</param>
    /// <param name="e">not used</param>
    private void nPhasesChanged(object sender, RoutedEventArgs e)
    {
        //...
    }
    /// <summary>Sets up the row headers</summary>
    /// <param name="sender">not used</param>
    /// <param name="e">Row to be modified</param>
    private void RowIndex(object sender, DataGridRowEventArgs e)
    {
        e.Row.Header = (e.Row.GetIndex() + 1).ToString();
    }
    public MainWindow()
    {
        Phases = new ObservableCollection<NPhase>();
        Phases.Add(new NPhase(3, "Protensao Inicial"));
        Phases.Add(new NPhase(28, "Carga Movel"));
        Phases.Add(new NPhase(365, "1 Ano"));
        Phases.Add(new NPhase(18250, "Vida Util"));
        IPhasesCollection = CollectionViewSource.GetDefaultView(Phases); 
        InitializeComponent();
    }
}

NTab.xaml NTab.xaml

<UserControl x:Class="WPF.NTab"
         x:Name="CableTab"
         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:charting="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
         xmlns:l="clr-namespace:WPF"
         mc:Ignorable="d" >
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <!-- ... -->
    </Grid.RowDefinitions>
    <Grid Grid.Row="2">
        <Grid.ColumnDefinitions>
            <!-- ... -->
        </Grid.ColumnDefinitions>
        <Label Grid.Column="2">Pull Phase</Label>
        <ComboBox Grid.Column="3"
                  Name="Phase"
                  ItemsSource="{Binding Phases, ElementName=CableTab}"/>
    </Grid>
</Grid>
</UserControl>

NTab.xaml.cs NTab.xaml.cs

public partial class NTab : UserControl
{
    ObservableCollection<string> Phases;
    public static DependencyProperty PhasesProperty = DependencyProperty.Register("Phases", typeof(ICollectionView), typeof(NTab));
    public ICollectionView IPhasesCollection
    {
        get { return (ICollectionView)GetValue(PhasesProperty); }
        set { SetValue(PhasesProperty, value); }
    }
    /// <summary>Updates the tab's Phase list to match the list in the Options tab.</summary>
    /// <param name="source">(ObservableCollection&lt;NPhase&gt;</param>
    /// <param name="e">not used.</param>
    public void PhasesChanged(object source, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        var pC = source as ObservableCollection<NPhase>;
        Phases.Clear();
        for (int i = 0; i < pC.Count; i++)
            Phases.Add("" + (i + 1));
    }
    public NTab(ObservableCollection<NPhase> p)
    {
        InitializeComponent();
        Phases = new ObservableCollection<string>();
        IPhasesCollection = CollectionViewSource.GetDefaultView(Phases);
        PhasesChanged(p, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
        p.CollectionChanged += PhasesChanged;
    }
}

You're looking at this in the wrong way... it is a rare situation in WPF when a UI control needs to know anything about another UI control. 您正在以错误的方式看待这个问题……当一个UI控件需要了解有关另一个UI控件的任何信息时,这在WPF中是一种罕见的情况。 It is much more common to work with the data . 处理数据更为常见。 By this, I mean that your NTab only needs to be aware of the data from the Phase DataGrid and not the control. 借此,我的意思是您的NTab只需要知道Phase DataGrid中的数据 ,而不是控件中的数据。

The simplest way to achieve this is to have one view model for the whole TabControl which shares its properties over the various TabItem s. 实现此目的的最简单方法是为整个TabControl使用一个视图模型,该视图模型在各种TabItem共享其属性。 So rather than trying to handle UI events to keep the collections the same, just use one collection for all of the TabItem s. 因此,与其尝试处理UI事件以使集合保持相同,不如对所有TabItem使用一个集合。 In the DataGrid , you could do this: DataGrid ,您可以执行以下操作:

<DataGrid ItemsSource="{Binding YourCollection}" ... />

On each additional TabItem , you could have this: 在每个其他TabItem ,您都可以这样做:

<ComboBox ItemsSource="{Binding YourCollection}" ... />

In this way, an update to the collection in one TabItem will automatically be reflected in all of the other TabItem s, without you having to do anything. 这样,一个TabItem集合的更新将自动反映在所有其他TabItem ,而您无需执行任何操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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