WPF, bindable richTextBox with MVVM (Caliburn.Micro)-Document already belongs to another RichTextBox

Dec 22, 2010 at 6:19 PM
Edited Dec 23, 2010 at 2:56 PM

Hi, I have a very interesting problem. I use this technologies in WPF app: Caliburn.Micro and MEF.

I open new window (not screen) from view model. It works good.

In Init-View-Model I have this method, which open new WPF Window, not screen in shell.

 

...

public IEnumerable<IResult> Send()
{
yield return new ShowWindow("NewScreen")
.InitializeWith(_service.DetailData(Account,_selectedFriend.Key));
}
...

 

ShowWindow class look like this:

 

public class ShowWindow : IResult
{
readonly Type _windowType;
readonly string _name;

[Import]
public IShellViewModel Shell { get; set; }

Action<object> _initializationAction = window => { };

public ShowWindow InitializeWith<T>(T argument)
{
_initializationAction = window =>
{
var initializable = window as IInitializable<T>;
if (initializable != null)
initializable.Initialize(argument);
};
return this;
}

public ShowWindow(string name)
{
_name = name;
}

public ShowWindow(Type windowType)
{
_windowType = windowType;
}

public void Execute(ActionExecutionContext context)
{
var window = !string.IsNullOrEmpty(_name)
? IoC.Get<object>(_name)
: IoC.GetInstance(_windowType, null);

_initializationAction(window);

IoC.Get<IWindowManager>().Show(window);

Completed(this, new ResultCompletionEventArgs());
}

public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };

public static ShowWindow Of<T>()
{
return new ShowWindow(typeof(T));
}
}

 

I use event aggregator on sending messages from init view model to new window.

Everything worked well until I added richtebox control. I need bindable richtextbox. So I

use bindable version of Jason Mueller (http://social.msdn.microsoft.com/forums/en-US/wpf/thread/f77c011a-0aba-449f-b6f4-920e58ebf997/)

New-View-Model look like this:

public class NewViewModel : Screen, IInitializable<DetailData>, IHandle<string>
{
    private IEventAggregator _eventAgg;

    private FlowDocument _conversation;

    //bind on document of richtextBox
    public FlowDocument Conversation
    {
        get { return _conversation; }
        set
        {
            _conversation = value;
            NotifyOfPropertyChange("Conversation");
        }
    }

    [ImportingConstructor]
    public NewViewModel(IEventAggregator eventAgg)
    {
        _eventAgg = eventAgg;
        _eventAgg.Subscribe(this);

        //I think problem is here
        _conversation = new FlowDocument();
    }

    public void Handle(string message)
    {
        Conversation.Blocks
            .Add(new Paragraph(new Run(message)));
    }
}

In  New-View-Model class I bind property Conversation on RichTextBox in View.

View:

 
   <Controls:BindableRichTextBox    Document="{Binding Path=Conversation, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
                                    VerticalScrollBarVisibility="Auto" 
                                    HorizontalScrollBarVisibility="Auto"
                                    FontSize="13"
                                    Margin="4,4,4,4" 
                                    Grid.Row="0" />


Problem is.

 

1. I call method  public IEnumerable<IResult> Send() from Init-View-Model  -> it called cotructor of New-View-Model -> and it opened new window. That is right

2. Than I call method public IEnumerable<IResult> Send()  second time and I get this error: System.Argument.Exception  {"Document already belongs to another RichTextBox."}

This error I get in class of bindable richTextBox.

 

...

        protected override void OnInitialized(EventArgs e)
        {
            // Hook up to get notified when DocumentProperty changes.
            DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(DocumentProperty, typeof(BindableRichTextBox));
            descriptor.AddValueChanged(this, delegate
            {
                // If the underlying value of the dependency property changes,
                // update the underlying document, also.
line 54:        base.Document = (FlowDocument)GetValue(DocumentProperty);

            });

            // By default, we support updates to the source when focus is lost (or, if the LostFocus
            // trigger is specified explicity.  We don't support the PropertyChanged trigger right now.
            this.LostFocus += new RoutedEventHandler(BindableRichTextBox_LostFocus);

            base.OnInitialized(e);

        }

....

 

I think problem is because it call only once constructor of New-View-Model. So I call five times method Send but it call constructor of New-View-Model only once.

How can solve it ?

 

StackTrace:

 

   at System.Windows.Controls.RichTextBox.set_Document(FlowDocument value)
   at Spirit.Controls.BindableRichTextBox.b__0(Object , EventArgs ) in C:\Users\Jan\Documents\Visual Studio 2010\Projects\C#\Pokec_Messenger\ver.beta\Pokec__Messenger\Spirit_v1.2\Controls\BindableRichTextBox.cs:line 54
   at MS.Internal.ComponentModel.PropertyChangeTracker.OnPropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
   at System.Windows.DependentList.InvalidateDependents(DependencyObject source, DependencyPropertyChangedEventArgs sourceArgs)
   at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
   at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
   at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp)
   at System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
   at System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
   at System.Windows.Data.BindingExpression.Activate(Object item)
   at System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt)
   at System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
   at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
   at MS.Internal.Data.DataBindEngine.Run(Object arg)
   at MS.Internal.Data.DataBindEngine.OnLayoutUpdated(Object sender, EventArgs e)
   at System.Windows.ContextLayoutManager.fireLayoutUpdateEvent()
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
   at System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.Resize(ICompositionTarget resizedCompositionTarget)
   at System.Windows.Interop.HwndTarget.OnResize()
   at System.Windows.Interop.HwndTarget.HandleMessage(WindowMessage msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Interop.HwndSource.HwndTargetFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)


Where is it problem, I try many ways but any doesn’t work. Thank for your advice and help. I am helpless.

 

[Export("ChatScreen", typeof(IChatViewModel))]
public class ChatViewModel : Screen, IInitializable<DetailData>, IHandle<string>
{
    private IEventAggregator _eventAgg;

    private FlowDocument _conversation;

    //bind on document of richtextBox
    public FlowDocument Conversation
    {
        get { return _conversation; }
        set
        {
            _conversation = value;
            NotifyOfPropertyChange("Conversation");
        }
    }

    [ImportingConstructor]
    public ChatViewModel(IEventAggregator eventAgg)
    {
        _eventAgg = eventAgg;
        _eventAgg.Subscribe(this);

        //I think problem is here
        _conversation = new FlowDocument();
    }

    public void Handle(string message)
    {
        Conversation.Blocks
            .Add(new Paragraph(new Run(message)));
    }
}
Dec 24, 2010 at 11:49 AM
Edited Dec 24, 2010 at 11:50 AM

This is probably because the default lifetime of MEF exports is Shared (singleton).

If you want a transient lifetime of your NewViewModel (everytime you call Send() a new instance of this viewmodel is created). You'll have to mark your NewViewModel with [PartCreationPolicy(CreationPolicy.NonShared)].

If you want to only use one instance of the NewViewModel (Shared/Singleton). You have to be sure you re-initialize the whole viewmodel (create a new instance of FlowDocument everytime you call Send()).

 

But it's probably best to go with method 1.

 

Remco Ros

Dec 25, 2010 at 10:06 AM
remcoros wrote:

This is probably because the default lifetime of MEF exports is Shared (singleton).

If you want a transient lifetime of your NewViewModel (everytime you call Send() a new instance of this viewmodel is created). You'll have to mark your NewViewModel with [PartCreationPolicy(CreationPolicy.NonShared)].

If you want to only use one instance of the NewViewModel (Shared/Singleton). You have to be sure you re-initialize the whole viewmodel (create a new instance of FlowDocument everytime you call Send()).

 

But it's probably best to go with method 1.

 

Remco Ros

Thank for help, I used first method. It help me a lot ;)