0

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.

Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.

Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.

Here is a sample of a command that I've implemented:

public viewModel()
{
    allowReversing = new Command(allowReversing_Operations);
}

public Command AllowReversing
{
       get { return allowReversing; }
} 

private Command allowReversing; 

private void allowReversing_Operations()
{
       //Query for Window1
       var mainWindow = Application.Current.Windows
           .Cast<Window1>()
           .FirstOrDefault(window => window is Window1) as Window1;

       if (mainWindow.checkBox1.IsChecked == true) //Checked
       {
           mainWindow.checkBox9.IsEnabled = true;
           mainWindow.groupBox7.IsEnabled = true;
       }
       else //UnChecked
       {
           mainWindow.checkBox9.IsEnabled = false;
           mainWindow.checkBox9.IsChecked = false;
           mainWindow.groupBox7.IsEnabled = false;
       }
} 

*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.

Now to the question:

After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads: enter image description here

I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)

Thank you for your help.

Revision of Leo's answer

This is the current version of Leo's answer that I have compiling. (There were some syntax errors)

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);
       }
}
Community
  • 1
  • 1
Eric after dark
  • 1,768
  • 4
  • 31
  • 79
  • Can you post the code where CircularButtonPrototypeHelpers.Command is? – 123 456 789 0 Sep 04 '13 at 20:13
  • 1
    If you want to clone it, the question is does the behavior of these tab items different from the others? How are you creating these tab items and when are they cloned? – 123 456 789 0 Sep 04 '13 at 20:14
  • @Leo The code under CircularButtonPrototype.Helpers.Command is an implementation of ICommand, it's about 190 lines. It was something I got out of this tutorial: http://www.codeproject.com/Articles/274982/Commands-in-MVVM#example1 I have like 5 different types of tabs. The user is allowed to create new ones though. So for example.. if a tab is green and the user wants to create a new green tab, the master green tab is cloned. To answer your other question, they are cloned dynamically. – Eric after dark Sep 04 '13 at 20:22
  • Hi Eric, it sounds like you're new to WPF/MVVM and if so you might want to check out the links in my answer [here](http://stackoverflow.com/a/15684569/302677) about transitioning from Winforms to WPF. In your case, all properties for the tab item such as `IsEnabled` and `IsChecked` should be on your ViewModel (or Model), and your XAML (`TabItem` in this case) is just a pretty user-friendly interface used to display the ViewModel to the user. To create a new "tab" you really want to create a new ViewModel and add it to the collection, which will automatically be drawn using a new TabItem – Rachel Sep 05 '13 at 00:11
  • So it's okay that I interacted with my main window through my commands then? – Eric after dark Sep 05 '13 at 17:06

2 Answers2

1

1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.

2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.

I have this code that I made

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

So it would be like

var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);
123 456 789 0
  • 10,565
  • 4
  • 43
  • 72
  • Okay, #1 worked great. Now in Implementing #2 what do I need to change in that function to make it specific to my program? And I think you may have meant: `newTabItem.CopyPropertiesFrom(masterTab);` – Eric after dark Sep 04 '13 at 20:35
  • Specifically I'm having trouble with `GetAllProperties`, `.ToDictionary`, and `dependencyValue`. – Eric after dark Sep 04 '13 at 20:46
  • It's an extension method so make sure the CopyPropertiesFrom is defined in a statis class. – 123 456 789 0 Sep 04 '13 at 20:48
  • And as long as it's in a `static class` it should work correctly? – Eric after dark Sep 04 '13 at 20:50
  • It should copy the values you literally want to clone to the new item. – 123 456 789 0 Sep 04 '13 at 20:52
  • @LeoLorenzoLuis of course this goes against all known good practices in WPF, and is a terribly bad advice for someone who is trying to implement a well-formed pattern. – Federico Berasategui Sep 04 '13 at 21:15
  • @HighCore What makes you say that? You know cloning is more terrible and serializing it then deserializing it just to clone it. – 123 456 789 0 Sep 04 '13 at 21:18
  • Then change the FrameworkContentElement to TabItem, it was a sample code. – 123 456 789 0 Sep 04 '13 at 21:18
  • @HighCore I know that but I don't think serializing and deserializing a UI just to clone is correct. I'm not even doing anything with the UI. It's like saying creating UI controls dynamically is saying 'UI is not data'? Thus you're breaking it then. Feel free to suggest him another option then. – 123 456 789 0 Sep 04 '13 at 21:21
  • @HighCore By the way, I didn't mention he should put this in the ViewModel. – 123 456 789 0 Sep 04 '13 at 21:22
  • Lmao, sure but no. It depends on the scenario where you certainly have to create the UI controls dynamically. There are still scenarios in WPF where you need to create the control dynamically/manipulate the dependency properties. Don't act like you know WPF very well. – 123 456 789 0 Sep 04 '13 at 21:25
  • I'll have to stand with HighCore on this. Procedural creation of UI controls in WPF suggests a lack of knowledge of what XAML is truly capable of. You can count the number of times you actually need to do so with the fingers of one hand. – Yandros Sep 04 '13 at 22:04
  • @HighCore Create an application that has a track changes mode (where it shows the user the inserted/deleted character) when someone pushes to them using FlowDocuments. – 123 456 789 0 Sep 04 '13 at 22:45
  • Oh, and keep in mind. The contents of the RichTextBox depends on the data coming from the database and when the user loses focus you need to make it non editable. – 123 456 789 0 Sep 04 '13 at 22:46
  • @HighCore and if you disagree with my solution then you are free to suggest the person who ask the question another solution that you think will help him solve his problem. How would you "clone" the tab item? Providing an argument where you do not provide any alternative is not helping here. – 123 456 789 0 Sep 04 '13 at 22:48
  • @HighCore Now you're saying it is a different matter, I just gave you a scenario and it falls under WPF. You can't put everything using attached properties because not everything is exposed in a given class in FrameworkContentElements, you need to dig deeper. May I ask you, how experienced are you with FrameworkContentElements? – 123 456 789 0 Sep 04 '13 at 22:49
  • You asked me what kind of scenario I would use a procedural creation of UI and I gave you one. Some arrogance in here, I see, – 123 456 789 0 Sep 04 '13 at 22:53
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36821/discussion-between-leo-lorenzo-luis-and-highcore) – 123 456 789 0 Sep 04 '13 at 22:56
  • Hey @Leo, do you mind double checking that function? Either I'm missing something or you are. If you have it implemented and working in a program you could also send it to me so that I can see it in action, if that's easier. Thanks. – Eric after dark Sep 05 '13 at 17:53
  • Updated with the GetAllProperties() – 123 456 789 0 Sep 05 '13 at 18:29
  • Okay got it. However, `.ToDictionary` and `dependancyValue` do not exist in the current context. Why do I receive this? – Eric after dark Sep 05 '13 at 19:15
  • Do you have reference to System.Linq? – 123 456 789 0 Sep 05 '13 at 19:18
  • Yes, I have System.Linq – Eric after dark Sep 05 '13 at 19:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36890/discussion-between-eric-after-dark-and-leo-lorenzo-luis) – Eric after dark Sep 05 '13 at 19:33
1

Here is my example of a properly-implemented dynamic TabControl in WPF.

The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.

The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.

This removes the need for "cloning" the UI or do any other weird manipulations with it.

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • That's just assuming that the TabItem control's DataContext is the TabViewModel where it shows the Data in the UI, it did not solve where he want to manipulate and copy the dependency properties of a TabItem, what are you gonna suggest here? Attach properties? Styles? DataTemplates? – 123 456 789 0 Sep 04 '13 at 22:52
  • Oh, so you're saying a SolidColorBrush must be in a ViewModel property because you said everything will be bound to the relevant properties in the ViewModel/Model which is not true? – 123 456 789 0 Sep 04 '13 at 22:55