Question

In my program I have tabItems that have their commands bound to a View Model. I am in the process of implementing a function that will copy the design structure of a "master" tabItem, along with it's command functionality in order to create a new tabItem. I need to do this because the user of this program will be allowed to add new tabItems.

Currently I am using the question Copying a TabItem with an MVVM structure, but I seem to be having trouble when the function tries to copy the Grid object using dependencyValue.

The class I am using:

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

When dependencyValue gets to {[Content, System.Windows.Controls.Grid]} the program throws an InvalidOperationException was Unhandled stating that, "Specified element is already the logical child of another element. Disconnect it first".

What does this mean? Is this a common problem with the Grid in WPF (am I breaking some rule by trying to do this?)? Is there something in my program that I am not aware of that is causing this?

Was it helpful?

Solution

Ok. This is how you're supposed to deal with a TabControl in WPF:

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

Code Behind:

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 (you to derive from this to create each different 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;
    }
}

Edit: I forgot to post the Command class (though you surely have your own)

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

And finally PropertyChangedBase (just a helper class)

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

Result:

enter image description here

  • Basically, each Tab Item type is a Widget, which contains its own logic and Data.
  • You define all logic and data at the ViewModel or Model level, and never at the UI level.
  • You manipulate the data defined in either the ViewModel or the Model level, and have the UI updated via DataBinding, never touching the UI directly.
  • Notice How I'm leveraging DataTemplates in order to provide a specific UI for each Tab Item ViewModel class.
  • When copying a new Tab, you just create a new instance of the desired ViewModel, and add it to the ObservableCollection. WPF's DataBinding automatically updates the UI based on the Collection's change notification.
  • If you want to create additional tab types, just derive from MVVMTabItemViewModel and add your logic and data there. Then, you create a DataTemplate for that new ViewModel and WPF takes care of the rest.
  • You never, ever, ever manipulate UI elements in procedural code in WPF, unless there's a REAL reason to do so. You don't "uncheck" or "disable" UI Elements because UI elements MUST reflect the STATE of the data which is provided by the ViewModel. So a "Check/Uncheck" state or an "Enabled/Disabled" state is just a bool property in the ViewModel to which the UI binds.
  • Notice how this completely removes the need for horrendous winforms-like hacks and also removes the need for VisualTreeHelper.ComplicateMyCode() kind of things.
  • Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top