Monday, 16 April 2012

ScrollViewer AutoScroll Behavior

Overview
This behavior causes a ScrollViewer to automatically scroll to the bottom when it's contents change size. It is useful for TextBox controls inside ScrollViewers so that the cursor doesn't disappear whilst typing.


The Behavior


    public class AutoScrollBehavior : Behavior<ScrollViewer>
    {
        private ScrollViewer _scrollViewer = null;
        private double _height = 0.0d;

        protected override void OnAttached()
        {
            base.OnAttached();

            this._scrollViewer = base.AssociatedObject;
            this._scrollViewer.LayoutUpdated += new EventHandler(_scrollViewer_LayoutUpdated);
        }

        private void _scrollViewer_LayoutUpdated(object sender, EventArgs e)
        {
            if (this._scrollViewer.ExtentHeight != _height)
            {
                this._scrollViewer.ScrollToVerticalOffset(this._scrollViewer.ExtentHeight);
                this._height = this._scrollViewer.ExtentHeight;
            }
        }     

        protected override void OnDetaching()
        {
            base.OnDetaching();

            if (this._scrollViewer != null)
                this._scrollViewer.LayoutUpdated -= new EventHandler(_scrollViewer_LayoutUpdated);
        }
    }

Implementation in XAML

<ScrollViewer Height="200">
    <i:Interaction.Behaviors>
        <cmd:AutoScrollBehavior />
    </i:Interaction.Behaviors>
    <TextBox IsEnabled="{Binding Path= IsCommentEnabled}" Text="{Binding Path=Comment, Mode=TwoWay}" MinHeight="200"
        AcceptsReturn="True" InputScope="Text" TextWrapping="Wrap">
    </TextBox>
/ScrollViewer>

Thursday, 12 April 2012

MVVM Light - Passing Params to Target ViewModel Before Navigating - Part II

After a lot more thinking about this, I've come up with a really neat solution, needs more testing, but here it is in current form. There's a MessageSender and MessageReceiver class which between them handle sending InitMessageBase type objects and allow buffering and requesting of a message if target is not constructed.


Example code here


Part 1 here


MessageSender


    public class MessageSender<T> where T : InitMessageBase
    {
        private T _message = default(T);

        public MessageSender()
        {
            // Send back item when a constructor asks for it
            Messenger.Default.Register<T>(this, message =>
            {
                if (this._message != null && this._message != message && !this._message.Received)
                {                     
                    // Send back original message
                    Messenger.Default.Send(this._message);
                }
            });
        }

        public void SendMessage(T message)
        {
            // Store value
            this._message = message;

            // Try and send message
            Messenger.Default.Send(message);
        }
    }

MessageReceiver

    public class MessageReceiver<T> where T : InitMessageBase
    {
        private Action<T> _handler = null;
        private T _message = null;

        public MessageReceiver(Action<T> handler)
        {
            this._handler = handler;

            // Wait for messages
            Messenger.Default.Register<T>(this, message =>
            {
                if (this._message != null && this._message != message && !message.Received)
                {
                    message.Received = true;

                    this.OnMessageReceived(message);
                }
            });
        }

        /// <summary>
        /// Send empty message on instantiation
        /// </summary>
        /// <param name="message"></param>
        public MessageReceiver(Action<T> handler, bool sendInitMessage)
            : this(handler)
        {
            if (sendInitMessage)
            {
                this._message = Activator.CreateInstance(typeof(T)) as T;

                // Send empty message
                Messenger.Default.Send<T>(this._message);
            }
        }

        private void OnMessageReceived(T message)
        {
            if (this._handler != null)
                this._handler( message );
        }
    }

InitMessageBase

    public abstract class InitMessageBase : MessageBase
    {
        // Used for a send
        public bool Received { get; set; }

        public InitMessageBase()
        {

        }
    }

An Implementation of InitMessageBase

    public class ArticleInitMessage : InitMessageBase
    {
        public  Article Value { get; set; }

        public ExampleInitMessage()
        {

        }

        public ExampleInitMessage( Article value)
        {
          this.Value = value;
        }
    }

Sending a Message
Simply create a global instance of the MessageSender and send a message or value:

if (this._articleSender == null)
this._articleSender = new MessageSender<ArticleInitMessage>();

this._articleSender.SendMessage(new ArticleInitMessage(article));

this._navService.NavigateTo(ViewModelLocator.ArticleUri);

Receiving a Message
Simply create a global instance of MessageReceiver at VM construct time and pass it an action to do:

this._articleReceiver = new MessageReceiver<ArticleInitMessage>((message) =>
{
this.SelectedItem = message.Value;
}, true);






Thursday, 5 April 2012

MVVM Light - Passing Params to Target ViewModel Before Navigating

Overview
I use MVVM Light for my WP7 apps, it's great! I implement the following navigation service for controlling navigation from the view model layer:


http://blog.galasoft.ch/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx


Part 2 Here


The main problem I've faced is that I often want to pass parameters to the target view's view model to prepare it for what it needs to do. It is possible to access properties in the target view model through the ViewModelLocator as each view model has a static property used for the view binding via the non-static properties, however this means that the view models are tightly coupled through the VML and it seems like a bit of a hack.


The example code is from an article page which requires navigation to a links page.


Messaging
It's possible to fire a message out before navigating to pass parameters to the target view model, however, if the target view has never been hit, the associated vm will not not have instantiated so will not receive the message! This can be worked around by calling the VML CreateXXX method however this is still not the elegant solution I was hoping for.


2-Way Messaging
To solve this problem, I figured that when a VM instantiates, ask if any other VM has any parameters for it then register a message pipe for parameters when it is instantiated.

The Message
I created a message which would take a parameter to pass from the source to the target or an action to request a parameter from the source, by the target:


public class LinksInitMessage : MessageBase
{
    public Article Payload { get; set; }
    public Action<Article> Callback { get; set; }

    public LinksInitMessage(Article payload)
    {
        this.Payload = payload;
    }

    public LinksInitMessage(Action<Article> callback)
    {
        this.Callback = callback;
    }
}


Source VM
First off messages need registering (at constructor stage) so that the target VM may message back one it constructs:


private void RegisterMessenger()
{
    //Register any messages
    Messenger.Default.Register<LinksInitMessage>(this, msg =>
    {
        if (this._linksInitItem != null)
        {
            msg.Callback(this._linksInitItem);
            this._linksInitItem = null;
        }
    });
}

Then when the source VM is ready to navigate , it can send out a message and keep a parameter in case the VM calls back, then navigate:

private void Link()
{
    // Send message to Links (it will get it if it's there otherwise, the VM will request when open)
    Messenger.Default.Send<LinksInitMessage>(new LinksInitMessage(this._selectedItem));

    // Store article incase Links requests it
    this._linksInitItem = this._selectedItem;

    // Nav to links
    _navService.NavigateTo(ViewModelLocator.LinksUri);


Target VM
First off when the VM constructs, it sends out a message to ask if any other VM has any a message for it, if it gets a response it can initialise itself. Second it registers a message type so once it is already instantiated, it can receive params again and re-initialise itself:

private void RegisterMessenger()
{
    //Send out a message to see if another VM has params we need
    Messenger.Default.Send<LinksInitMessage>(new LinksInitMessage(article =>
    {
        if (article != null)
        {
            this.SelectedItem = article;
        }
    }));

    //Register any message pipes
    Messenger.Default.Register<LinksInitMessage>(this, msg =>
    {
        this.ClearViewModel();
        this.SelectedItem = msg.Payload;
    });
}

Conclusion
That's it, seems like a nice loosely coupled way of initialising a view model on navigation.