Can I have multiple instances of the same UserCont

2019-07-03 18:01发布

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

2条回答
Evening l夕情丶
2楼-- · 2019-07-03 18:11

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

查看更多
爷、活的狠高调
3楼-- · 2019-07-03 18:21

You might be interested in the Writer sample application of the WPF Application Framework (WAF). It shows how to implement a text processing application with multiple tab support by applying the MVVM pattern.

查看更多
登录 后发表回答