我正在尝试在我的下一个WP7应用程序(用 Vb.NET 编写)中提高我的MVVM能力。 我有一个文本框,该文本框已获得焦点并显示WP7键盘。 我正在使用命令绑定和 xyzzer 的可绑定应用程序栏(这非常好)。
http://bindableapplicationb.codeplex.com/
我希望能够通过在窗体上设置焦点来从视图模型中取消文本框的焦点。 通常(非 MVVM)我会通过调用以下命令从表单中执行此操作:
Me.Focus()
但是我不能从视图模型执行此操作(我不应该这样做)。 目前,我正在从ViewModel中引发一个事件并在表单上捕获它,但它令人讨厌。 有没有 MVVM 友好的方法来做到这一点? 到目前为止,我还没有使用过工具包,因为 vb.net 中的例子有限。
我一直在使用命令绑定。
您可以尝试使用以下行为:
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); }
}
}
然后在 xaml 中:
<TextBox>
<i:Interaction.Behaviors>
<behaviors:FocusBehavior HasInitialFocus="True"
IsFocused="{Binding IsFocused}" />
</i:Interaction.Behaviors>
</TextBox>
让我猜猜:问题是当您单击应用程序栏图标按钮时,文本框尚未更新视图模型上的绑定属性,对吗?
使用来自 Cimbalino Windows Phone Toolkit 的 ApplicationBarBehavior(您也可以从 NuGet 获取它),它在内部处理它 - 因此在 ApplicationBarIconButton 单击事件完成之前,它已经更新了 TextBox.Text 绑定属性!
检查 GitHub 中的示例代码,您就可以使用它了!
编辑:
如果您只想在页面上设置焦点(从而在 TextBox 失去焦点后关闭键盘),我会使用外部类来完成这项工作,然后在 ViewModel 中使用它,如下所示:
//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()");
}
}
然后,在 ViewModel 上,创建一个如下所示的服务实例:
public class MyViewModel
{
private IPageService _pageService;
public MyViewModel()
{
#if USE_MOCKUP
_pageService = new PageServiceMockup();
#else
_pageService = new PageService();
#endif
}
}
当您想在页面上设置焦点时,您所要做的就是调用 _pageService.Focus()
.
这是解决问题的完全 MVVM 方法!
使用 Pedros 示例以及我之前在我的应用程序中实现的其他服务,我在 vb.net 中将以下解决方案组合在一起:
创建一个 IFocus 接口,此接口可以由焦点服务或模拟实现
Public Interface IFocusInterface
Sub Focus()
End Interface
创建 IFocable 接口。 这将由 ViewModel 实现,并接受实现 IFocusInterface 的对象。
Public Interface IFocusable
Property FocusService As IFocusInterface
End Interface
使用单例模式实现焦点接口
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
在视图模型中实现 IFocusable,并确保在构造视图模型后将焦点服务单一实例传递到 ViewModel。
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