无法将Grid对象从一个TabItem复制到另一个TabItem

时间:2013-09-09 17:41:02

标签: c# wpf mvvm grid tabitem

在我的程序中,我有tabItems将其命令绑定到View Model。我正在实现一个函数,它将复制“master”tabItem的设计结构及其命令功能,以便创建一个新的tabItem。我需要这样做,因为该程序的用户将被允许添加新的tabItems

目前我使用的是问题Copying a TabItem with an MVVM structure,但当函数尝试使用Grid复制dependencyValue对象时,我似乎遇到了麻烦。

我正在使用的课程:

public static class copyTabItems
{
    public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
    {
        return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
                    select DependencyPropertyDescriptor.FromProperty(pd)
                    into dpd
                    where dpd != null
                    select dpd.DependencyProperty).ToList();
    }

    public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
                                                   FrameworkElement controlToCopy)
    {
        foreach (var dependencyValue in GetAllProperties(controlToCopy)
                .Where((item) => !item.ReadOnly)
                .ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
        {
            controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
        }
    }
}

dependencyValue到达{[Content, System.Windows.Controls.Grid]}时,程序会抛出一个InvalidOperationException was Unhandled,指出“指定的元素已经是另一个元素的逻辑子元素。首先断开它”。

这是什么意思?这是WPF中Grid的常见问题(我是否通过尝试执行此操作来破坏某些规则?)?我的程序中有什么东西我不知道是什么导致了这个?

1 个答案:

答案 0 :(得分:3)

确定。这就是你应该如何在WPF中处理TabControl

<Window x:Class="MiscSamples.MVVMTabControlSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        Title="MVVMTabControlSample" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:Tab1ViewModel}">
            <!-- Here I just put UI elements and DataBinding -->
            <!-- You may want to encapsulate these into separate UserControls or something -->
            <StackPanel>
                <TextBlock Text="This is Tab1ViewModel!!"/>
                <TextBlock Text="Text1:"/>
                <TextBox Text="{Binding Text1}"/>
                <TextBlock Text="Text2:"/>
                <TextBox Text="{Binding Text2}"/>
                <CheckBox IsChecked="{Binding MyBoolean}"/>
                <Button Command="{Binding MyCommand}" Content="My Command!"/>
            </StackPanel>
        </DataTemplate>

        <!-- Here you would add additional DataTemplates for each different Tab type (where UI and logic is different from Tab 1) -->
    </Window.Resources>

    <DockPanel>
        <Button Command="{Binding AddNewTabCommand}" Content="AddNewTab"
                DockPanel.Dock="Bottom"/>

        <TabControl ItemsSource="{Binding Tabs}"
                    SelectedItem="{Binding SelectedTab}"
                    DisplayMemberPath="Title">

        </TabControl>
    </DockPanel>
</Window>

代码背后:

public partial class MVVMTabControlSample : Window
{
    public MVVMTabControlSample()
    {
        InitializeComponent();

        DataContext = new MVVMTabControlViewModel();
    }
}

Main ViewModel:

public class MVVMTabControlViewModel: PropertyChangedBase
{
    public ObservableCollection<MVVMTabItemViewModel> Tabs { get; set; }

    private MVVMTabItemViewModel _selectedTab;
    public MVVMTabItemViewModel SelectedTab
    {
        get { return _selectedTab; }
        set
        {
            _selectedTab = value;
            OnPropertyChanged("SelectedTab");
        }
    }

    public Command AddNewTabCommand { get; set; }

    public MVVMTabControlViewModel()
    {
        Tabs = new ObservableCollection<MVVMTabItemViewModel>();
        AddNewTabCommand = new Command(AddNewTab);
    }

    private void AddNewTab()
    {
        //Here I just create a new instance of TabViewModel
        //If you want to copy the **Data** from a previous tab or something you need to 
        //copy the property values from the previously selected ViewModel or whatever.

        var newtab = new Tab1ViewModel {Title = "Tab #" + (Tabs.Count + 1)};
        Tabs.Add(newtab);

        SelectedTab = newtab;
    }
}

Abstract TabItem ViewModel(你可以从中派生出来创建每个不同的Tab“Widget”)

public abstract class MVVMTabItemViewModel: PropertyChangedBase
{
    public string Title { get; set; }

    //Here you may want to add additional properties and logic common to ALL tab types.
}

TabItem 1 ViewModel:

public class Tab1ViewModel: MVVMTabItemViewModel
{
    private string _text1;
    private string _text2;
    private bool _myBoolean;

    public Tab1ViewModel()
    {
        MyCommand = new Command(MyMethod);
    }

    public string Text1
    {
        get { return _text1; }
        set
        {
            _text1 = value;
            OnPropertyChanged("Text1");
        }
    }

    public bool MyBoolean
    {
        get { return _myBoolean; }
        set
        {
            _myBoolean = value;
            MyCommand.IsEnabled = !value;
        }
    }

    public string Text2
    {
        get { return _text2; }
        set
        {
            _text2 = value;
            OnPropertyChanged("Text2");
        }
    }

    public Command MyCommand { get; set; }

    private void MyMethod()
    {
        Text1 = Text2;
    }
}

编辑:我忘了发布Command类(虽然你肯定有自己的)

public class Command : ICommand
{
    public Action Action { get; set; }

    public void Execute(object parameter)
    {
        if (Action != null)
            Action();
    }

    public bool CanExecute(object parameter)
    {
        return IsEnabled;
    }

    private bool _isEnabled = true;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            _isEnabled = value;
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }

    public event EventHandler CanExecuteChanged;

    public Command(Action action)
    {
        Action = action;
    }
}

最后是PropertyChangedBase(只是一个辅助类)

    public class PropertyChangedBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) 
               handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

结果:

enter image description here

  • 基本上,每个标签项类型都是Widget,其中包含自己的逻辑和数据。
  • 您可以在ViewModel或Model级别定义所有逻辑和数据,而不是在UI级别定义。
  • 您操作在ViewModel或模型级别中定义的数据,并通过DataBinding更新UI,从不直接触摸UI。
  • 请注意我是如何利用DataTemplates为每个Tab Item ViewModel类提供特定的UI。
  • 复制新标签时,只需创建所需ViewModel的新实例,然后将其添加到ObservableCollection即可。 WPF的DataBinding会根据Collection的更改通知自动更新UI。
  • 如果您想创建其他标签类型,只需从MVVMTabItemViewModel派生并在那里添加您的逻辑和数据。然后,为新的ViewModel创建DataTemplate,WPF负责其余的工作。
  • 你永远不会永远操纵WPF中程序代码中的UI元素,除非有真正的理由这样做。您不“取消选中”或“禁用”UI元素,因为UI元素必须反映ViewModel提供的数据的STATE。因此,“检查/取消选中”状态或“启用/禁用”状态只是UI绑定到的ViewModel中的bool属性。
  • 请注意这是如何完全消除对可怕的winforms-hacks的需求,也消除了VisualTreeHelper.ComplicateMyCode()种事情的需要。
  • 将我的代码复制并粘贴到File -> New Project -> WPF Application中,然后自行查看结果。