c# - 将 Grid 对象从一个 TabItem 复制到另一个时出现问题

标签 c# wpf mvvm grid tabitem

在我的程序中,我有 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声明,“指定的元素已经是另一个元素的逻辑子元素。首先断开它”。

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

最佳答案

行。这就是你应该如何处理 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();
    }
}

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

结果:

enter image description here
  • 基本上,每个选项卡项类型都是一个 Widget ,其中包含自己的逻辑和数据。
  • 您在 ViewModel 或 Model 级别定义所有逻辑和数据,而不是在 UI 级别。
  • 您操作在 ViewModel 或 Model 级别中定义的数据,并通过 DataBinding 更新 UI,从不直接接触 UI。
  • 注意我如何利用 DataTemplates 为每个选项卡项 ViewModel 类提供特定的 UI。
  • 复制新选项卡时,您只需创建所需 ViewModel 的新实例,并将其添加到 ObservableCollection 。 WPF 的 DataBinding 会根据 Collection 的更改通知自动更新 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/

    相关文章:

    c# - 将字节字符串转换为十六进制字符串的内置函数

    c# - Windows - C#- Directory.Move

    c# - 将 KeyBinding 命令绑定(bind)到 WPF DatePicker (MVVM)

    C# If Button IsEnabled,点击触发事件

    c# - 文本 block 的数据触发器

    c# - 使用数据注释的 WPF MVVM 验证

    c# - 字段中没有 @ 会导致它崩溃

    c# - 我的网站在没有 PDB 文件的情况下崩溃

    wpf - 将实现与模式的某些部分匹配

    java - 观察通过改造更改的实时数据不会在更改时触发