1

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

目前我正在使用Copying a TabItem with an MVVM structureGrid的问题,但是当函数尝试使用复制对象时我似乎遇到了麻烦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声明,“指定元素已经是另一个元素的逻辑子元素。首先断开它”。

这是什么意思?这是GridWPF 中的常见问题(我是否通过尝试这样做违反了某些规则?)?我的程序中是否有我不知道的东西导致了这种情况?

4

1 回答 1

3

好的。这就是你应该如何处理TabControlWPF 中的 a:

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

主视图模型:

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 视图模型:

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

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

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

结果:

在此处输入图像描述

  • 基本上,每个 Tab Item 类型都是一个Widget,它包含自己的逻辑和数据。
  • 您在 ViewModel 或 Model 级别定义所有逻辑和数据,而不是在 UI 级别。
  • 您可以操作在 ViewModel 或 Model 级别中定义的数据,并通过DataBinding更新 UI ,从不直接接触 UI。
  • 请注意我如何利用DataTemplates为每个 Tab Item ViewModel 类提供特定的 UI。
  • 复制新选项卡时,您只需创建所需的新实例ViewModel,并将其添加到ObservableCollection. WPF 的 DataBinding 会根据 Collection 的更改通知自动更新 UI。
  • 如果您想创建其他选项卡类型,只需从MVVMTabItemViewModel那里派生并添加您的逻辑和数据。DataTemplate然后,您为该新 ViewModel创建一个,而 WPF 会负责其余的工作。
  • 除非有真正的理由,否则您永远不会在WPF 的过程代码中操作 UI 元素。您不能“取消选中”或“禁用”UI 元素,因为 UI 元素必须反映 ViewModel 提供的数据的状态。因此,“检查/取消检查”状态或“启用/禁用”状态只是boolUI 绑定到的 ViewModel 中的一个属性。
  • 请注意,这如何完全消除了对可怕的类似 winforms 的黑客攻击的需要,也消除了对VisualTreeHelper.ComplicateMyCode()某种东西的需要。
  • 将我的代码复制并粘贴到 a 中File -> New Project -> WPF Application,然后自己查看结果。
于 2013-09-09T18:18:15.287 回答