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

当前,我正在使用Copying a TabItem with an MVVM structure问题,但是当函数尝试使用Grid复制dependencyValue对象时,似乎出现了问题。

我正在使用的类(class):

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的常见问题吗(我是通过这样做来打破某些规则吗?)?我的程序中是否有某些我不知道的原因导致此?

最佳答案

好的。这是您应该如何在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();
    }
}

主 View 模型:
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;
    }
}

抽象TabItem ViewModel(您可以从中派生创建每个不同的Tab“小部件”)
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));
        }
    }

结果:

  • 基本上,每个Tab Item类型都是Widget,它包含自己的逻辑和数据。
  • 您可以在ViewModel或Model级别(而不是在UI级别)定义所有逻辑和数据。
  • 您可以操纵在ViewModel或Model级别中定义的数据,并通过DataBinding更新UI,而切勿直接触摸UI。
  • 注意,我如何利用DataTemplates为每个Tab Item ViewModel类提供特定的UI。
  • 复制新的Tab时,只需创建所需ViewModel的新实例,然后将其添加到ObservableCollection中。 WPF的DataBinding根据集合的更改通知自动更新UI。
  • 如果要创建其他选项卡类型,只需从MVVMTabItemViewModel派生并在其中添加逻辑和数据即可。然后,为该新的ViewModel创建一个DataTemplate,WPF负责其余的工作。
  • 除非有真正的理由,否则永远不要永远会在WPF的过程代码中操纵UI元素。您不要“取消选中”或“禁用” UI元素,因为UI元素必须反射(reflect)ViewModel提供的数据的状态。因此,“检查/取消检查”状态或“启用/禁用”状态只是UI绑定(bind)到的ViewModel中的bool属性。
  • 请注意,这如何完全消除了对可怕的Winforms式黑客的需求,也消除了对VisualTreeHelper.ComplicateMyCode()之类的东西的需求。
  • 将我的代码复制并粘贴到File -> New Project -> WPF Application中,然后亲自查看结果。
  • 关于c# - 无法将Grid对象从一个TabItem复制到另一个,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18703908/

    10-12 22:59