1

I'm attempting to improve my MVVM abilities in my next WP7 App (which is written in Vb.NET). I have a textbox that has been given focus and has a WP7 keyboard displayed. I am using command binding and a xyzzer's bindable Application bar (which is excellent).

http://bindableapplicationb.codeplex.com/

I want to be able to cancel the focus of the TextBox from the ViewModel, by setting focus on the form. Normally (Non MVVM) I would do this from within the form by calling:

  Me.Focus()

But I can't do this from the ViewModel (and I shouldn't). At the moment I am raising an event out of the ViewModel and catching it on the form but its nasty. Is there a MVVM Friendly way of doing it? I haven't been using a Toolkit so far as there are limited examples in vb.net.

I have been using command binding.

JonAlb
  • 1,702
  • 10
  • 14

3 Answers3

2

Let me guess: the problem is that when you click the ApplicationBarIconButton, the TextBox hasn't updated yet the binded property on the ViewModel, correct?

Use the ApplicationBarBehavior from the Cimbalino Windows Phone Toolkit (you can get it from NuGet also), that handles that internally - so before the ApplicationBarIconButton click event gets done, it has already updated the TextBox.Text binded property!

Check the sample code in GitHub and you're all set to use it!

Edit:

If all you want is to set the focus on the page (and thus closing the keyboard after the TextBox looses focus), I'd go with an external class to do the job, and then use it in the ViewModel, something like this:

//This is the service interface
public interface IPageService
{
    void Focus();
}
//This implements the real service for runtime 
public class PageService : IPageServiceusage
{
    public void Focus()
    {
        var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;

        if (rootFrame == null)
            return;

        var page = rootFrame.Content as PhoneApplicationPage;

        if (page == null)
            return;

        page.Focus();
    }
}

//This implements the mockup service for testing purpose
public class PageServiceMockup : IPageService
{
    public void Focus()
    {
        System.Diagnostics.Debug.WriteLine("Called IPageService.Focus()");
    }
}

Then, on your ViewModel, create an instance of the service like this:

public class MyViewModel
{
    private IPageService _pageService;

    public MyViewModel()
    {
#if USE_MOCKUP
        _pageService = new PageServiceMockup();
#else
        _pageService = new PageService();
#endif
    }
}

And when you want to set the focus on the page, all you have to do is call _pageService.Focus().

This is a fully MVVM'ed way of solving the problem!

Pedro Lamas
  • 7,185
  • 4
  • 27
  • 35
  • No I don't have the problem, but thanks for your suggestion. I want make the Wp7 keyboard disappear by pressing a cancel button on the application bar. I know I can do this by calling me.focus() on the form but I can't do this from the ViewModel. – JonAlb Apr 15 '12 at 21:44
  • @JonAlb, I just updated the post with more info: now you can see a fully usable MVVM'ed service to set the focus on the page, as you requested! – Pedro Lamas Apr 16 '12 at 09:22
  • Thanks, My project is in vb.net. I'm at work but I will attempt to translate this into vb.net later. (unless anyone beats me to it) – JonAlb Apr 16 '12 at 12:33
2

You can try using a behavior:

public class FocusBehavior : Behavior<Control>
    {
        protected override void OnAttached()
        {
            AssociatedObject.GotFocus += (sender, args) => IsFocused = true;
            AssociatedObject.LostFocus += (sender, a) => IsFocused = false;
            AssociatedObject.Loaded += (o, a) => { if (HasInitialFocus || IsFocused) AssociatedObject.Focus(); };

            base.OnAttached();
        }

        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.Register(
                "IsFocused",
                typeof (bool),
                typeof (FocusBehavior),
                new PropertyMetadata(false, (d, e) => { if ((bool) e.NewValue) ((FocusBehavior) d).AssociatedObject.Focus(); }));

        public bool IsFocused
        {
            get { return (bool) GetValue(IsFocusedProperty); }
            set { SetValue(IsFocusedProperty, value); }
        }

        public static readonly DependencyProperty HasInitialFocusProperty =
            DependencyProperty.Register(
                "HasInitialFocus",
                typeof (bool),
                typeof (FocusBehavior),
                new PropertyMetadata(false, null));

        public bool HasInitialFocus
        {
            get { return (bool) GetValue(HasInitialFocusProperty); }
            set { SetValue(HasInitialFocusProperty, value); }
        }
    }

Then in xaml:

 <TextBox>
            <i:Interaction.Behaviors>
                <behaviors:FocusBehavior HasInitialFocus="True"
                                         IsFocused="{Binding IsFocused}" />
            </i:Interaction.Behaviors>
        </TextBox>
Derek Beattie
  • 9,429
  • 4
  • 30
  • 44
0

Using Pedros example, and the other services I have previously implemented in my App I put together the following solution in vb.net:

Create an IFocus Interface, This Interface can be implemented by the focus service or a Mock

Public Interface IFocusInterface
    Sub Focus()
End Interface

Create a IFocusable Interface. This will be implemented by the ViewModel and accepts an object that implements IFocusInterface.

Public Interface IFocusable
    Property FocusService As IFocusInterface
End Interface

Implement the Focus Interface with a singleton pattern

Imports Microsoft.Phone.Controls

Public NotInheritable Class FocusService
    Implements IFocusInterface

    Private Sub New()
    End Sub

    Private Shared ReadOnly m_instance As New FocusService
    Public Shared ReadOnly Property Instance() As FocusService
        Get
            Return m_instance
        End Get
    End Property

    Public Sub Focus() Implements IFocusInterface.Focus
        Dim rootFrame = TryCast(Application.Current.RootVisual, PhoneApplicationFrame)
        If Not rootFrame Is Nothing Then

            Dim page = TryCast(rootFrame.Content, PhoneApplicationPage)

            If Not page Is Nothing Then
                page.Focus()
            Else
                Throw New Exception("Unable to Cast the Root Frame Content into an Application Page")
            End If

        Else
            Throw New Exception("Unable to Cast the RootVisual into a PhoneApplicationFrame")
        End If

    End Sub

End Class

In your ViewModel Implement IFocusable, and make sure you pass in the Focus Service Singleton into the ViewModel after the ViewModel is Constructed.

Public Class MyViewModel
    Implements INotifyPropertyChanged
    Implements IFocusable

    ' Property for the Focus Service
    <Xml.Serialization.XmlIgnore()> Public Property FocusService As IFocusInterface Implements IFocusable.FocusService

    Public Sub Focus()
        If Not FocusService Is Nothing Then
            FocusService.Focus()
        Else
            Throw New Exception("ViewModel hasn't been passed a Focus Service")
        End If
    End Sub

End Class

Dim tMyViewModel as New MyViewModel
tMyViewModel.FocusService = Vacation_Calc_Model.FocusService.Instance
JonAlb
  • 1,702
  • 10
  • 14
  • The nice part about using a behavior is being able to attach it to other controls for actually setting focus via a simple binding and without taking a dependency on a service that only sets focus on the page. – Derek Beattie Apr 27 '12 at 20:03