views:

118

answers:

2

I am creating a Text Editor Type app. I can have multiple editors open via tabs. In my first try, I used simple TextBoxes to edit text. Everything worked ok. Then I created a UserControl encapsulating the text box + buttons to perform text manipulation eg. bold/italic etc. I found out that when I open different tabs, they all contain the same content. eg. In Tab1 i enter "hello world" that will appear in all tabs. There is "no separation" even though they are in different tabs

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:EditorTabViewModel}">
        <me:MarkdownEditor />
    </DataTemplate>
</Window.Resources>

I then as a test, tried a textbox and the usercontrol together to see if I have the same problem.

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:EditorTabViewModel}">
        <StackPanel>
            <me:MarkdownEditor Text="{Binding Content}" Height="360" />
            <TextBox Text="{Binding Content}" Height="360" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>

I then discovered a few strange things. With a new document, where content should be nothing, my MarkdownEditor had "System.Windows.Controls.Grid" in its text box as it a Grid was bound to the text. Text simple TextBox works as expected. Also I still had the same problem with all UserControls in the app having the same content.

The XAML for the UserControl

<UserControl x:Class="MarkdownEditMVVM.Controls.MarkdownEditor.MarkdownEditor" ...>
    <Grid>
        <ToolBar Grid.Row="0">
            <Button Command="{x:Static local:Commands.PreviewCommand}">
                <Image Source="../../Images/16/zoom.png" />
            </Button>
            <!-- more buttons -->
        </ToolBar>
        <TextBox Grid.Row="1" x:Name="txtEditor" AcceptsReturn="True" Text="{Binding Path=Text, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</UserControl>


Update

I discovered that this has something to do with the way I have usercontrols rendered in the tabs where tabs are bound to ObservableCollection<TabViewModel>

Suppose I have a TabViewModel even just a empty class

public class TabViewModel {}

Then in my Window

public partial class Window1 : Window
{
    protected ObservableCollection<TabViewModel> _tabs;
    protected ICollectionView _tabsCollectionView;

    public Window1()
    {
        InitializeComponent();
        this.DataContext = this;
        _tabs = new ObservableCollection<TabViewModel>();
        _tabs.Add(new TabViewModel());
        _tabs.Add(new TabViewModel());
        _tabsCollectionView = CollectionViewSource.GetDefaultView(_tabs);
    }

    public ICollectionView Tabs
    {
        get { return _tabsCollectionView; }            
    }
}

XAML

<TabControl ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate>
            <TextBox />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

I have created a simple Visual Studio Project hosted @mediafire illustrating this problem. I have a feeling it got something to do with the TabViewModels or the Tab Data Templates


Update 2

I further tested the user control problem by adding another tab control, this time without the TabViewModel

<TabControl Grid.Column="1">
    <TabItem Header="Tab 1">
        <TextBox />
    </TabItem>
    <TabItem Header="Tab 2">
        <TextBox  />
    </TabItem>
</TabControl>

It all worked ok. update also posted to mediafire


Update 3

Made yet another finding. This time, I tested with a binding. things worked fine ...

<TabControl Grid.Column="2" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabTitle}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Text}" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

Update also posted @mediafire


Update 4

Ok now I did a more complete set of tests

alt text

  • Row 0, Column 0: TextBox, Tabs bound to ObservableCollection<TabViewModel>. No Bindings for TextBox. Problem observed
  • Row 0, Column 1: TextBox, Normal TabItems, Tabs not bound to ObservableCollection<TabViewModel>. No Binding for TextBox. No problems
  • Row 0, Column 2: TextBox, Tabs bound to ObservableCollection<TabViewModel>. No Bindings for TextBox. No Problems
  • Row 1, Column 0: UserControl, Tabs bound to ObservableCollection<TabViewModel>. No Bindings for UserControl. Problem observed
  • Row 1, Column 2: UserControl, Tabs bound to ObservableCollection<TabViewModel>. Bindings for UserControl. Problem observed. Text bindings not working

Update @mediafire

+3  A: 

Big Edit in response to updates

I can get your mediafire example to work correctly by taking the following steps:

  1. Remove the dependency property Text from your user control - you don't need it
  2. Change your ContentTemplate on the TabControl to the below. This causes the UserControl.DataContext property to be set to the tab item DataContext

     <DataTemplate>
          <local:UserControl1 />
     </DataTemplate>
    
  3. Change your UserControl to the below. This binds the Text property to the UserControl.DataContext.Text property.

    <TextBox Text="{Binding Text}" />
    
  4. Remove the line this.DataContext = this from the constructor of UserControl1 - this will obviously replace the DataContext for the user control.

This caused the tabs to be correctly bound with the expected values in the sample app you uploaded


Original Answer

You can have multiple instances of a UserControl - that isn't your problem here.

The reason you are seeing the "System.Windows.Controls.Grid" text is because in your UserControl you are binding the Text property to this.DataContext.Text instead of this.Text - the property of your UserControl.

I think what you want to do is change the TextBox binding in your user control to:

<TextBox Grid.Row="1" x:Name="txtEditor" AcceptsReturn="True"
Text="{Binding Path=Text,
        UpdateSourceTrigger=PropertyChanged,
        RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type me:MarkDownEditor}}}" />

Note: this relies on the me namespace being set up to point to wherever your MarkDownEditor is located

Steve Greatrex
hmm, I tried that and got a StackOverflow Exception. I still dont really get the data context problem ... maybe because I never used `RelativeSource` too
jiewmeng
That was my mistake - I was binding to the property on the `TextBlock` instead of on the `MarkDownEditor` :s - Post edited to correct problem
Steve Greatrex
hmm, this is strange I still get the same Grid problem ... I also tried creating a custom control, more barebones in a clean application, I didn't have such a problem ... must have done something wrong somewhere, have you got any advice on such problems? as I develop, I somehow introduce wierd bugs tho they work ok alone. ie. when I have only 1 user control it works fine. should I just rewrite everything, then copy and paste functionality one at a time?
jiewmeng
Unfortunately, debugging behaviour in WPF apps is generally a bit tricky as you often can't set breakpoints. If I am getting the kind of behaviour that you are, I would generally change the binding of the property to something as simple as possible (e.g. `{Binding}`) and see what value that generates, and then work from there. You can also set up an `IValueConverter` on the `Binding` in which you can specify a breakpoint if you are really struggling...
Steve Greatrex
See update, I don't think the problem is with the bindings, I removed all bindings and I still have the same problem.
jiewmeng