我正在尝试找到一种使用表单行为的好方法,以确保用户只能在输入控件中键入所需的输入。我的问题是 xaml 框架永远不会调用 OnDetachingFrom 方法。这会导致内存丢失,因为我订阅了 Entry 控件的 TextChanged 事件来修改其行为,但无法取消订阅。
我试图找到一种“干净”的方法来跟踪哪些控件需要在页面从堆栈中弹出时清除其行为(我必须使用主导航页面来跟踪自己),但我能想到的一切命名每个控件,将控件添加到代码后面的页面上的集合中,使用“Clear”方法在页面上实现一个接口(interface),该方法对集合中的每个控件执行 xxx.Behaviors.Clear() ,调用 OnDetachingForm每个控件的方法。
这看起来有点可怕,与“干净”相反。我希望有人知道更好的方法。由于这样的设计疏忽,我从来没有真正喜欢过 XAML 和 MVVM。希望我所有的谷歌搜索都错过了一些东西。
对于我的行为,我几乎是从 Microsoft 教程页面复制的。
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/behaviors/creating
namespace StoreTrak.Behaviors
{
public class IntegerValidationBehavior : Behavior<Entry>
{
protected override void OnAttachedTo(Entry bindable)
{
if (bindable != null)
bindable.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(bindable);
}
/// <summary>
/// This NEVER gets called by the XAML framework.
/// </summary>
/// <param name="bindable"></param>
protected override void OnDetachingFrom(Entry bindable)
{
if (bindable != null)
bindable.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(bindable);
}
private static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (string.IsNullOrEmpty(args.NewTextValue))
{
((Entry)sender).Text = "0";
return;
}
if (!int.TryParse(args.NewTextValue, out int x))
((Entry)sender).Text = args.OldTextValue;
}
}
}
然后我只是做了一个基本的实现。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:StoreTrak.ViewModels"
xmlns:behaviors="clr-namespace:StoreTrak.Behaviors"
x:Class="StoreTrak.Pages.TestPage">
<ContentPage.BindingContext>
<vm:TestViewModel />
</ContentPage.BindingContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Text="Field 1" Grid.Row="0" Grid.Column="0" />
<Entry Text="{Binding Field1}" Grid.Row="0" Grid.Column="1" />
<Label Text="Field 2" Grid.Row="2" Grid.Column="0" />
<StackLayout Grid.Row="2" Grid.Column="1" Margin="0" Padding="0">
<Entry x:Name="Field2" Text="{Binding Field2}">
<Entry.Behaviors>
<behaviors:IntegerValidationBehavior />
</Entry.Behaviors>
</Entry>
<Label Text="Error number 1" TextColor="Red" FontSize="Small" IsVisible="False" />
<Label Text="Error number 2" TextColor="Red" FontSize="Small" IsVisible="True" />
<Label Text="Error number 3" TextColor="Red" FontSize="Small" IsVisible="False" />
</StackLayout>
<Label Text="Field 3" Grid.Row="3" Grid.Column="0" />
<Entry Text="{Binding Field3}" Grid.Row="3" Grid.Column="1" />
</Grid>
那么我如何才能触发该事件呢?我能找到的唯一方法是调用
Field2.Behaviors.Clear();
但是我在哪里调用它呢?我不能将它放在 OnApprearing 中,因为它可以在导航到新页面时调用,然后当再次显示此页面时,行为就会消失。
/// <summary>
/// can be called when a new page is added to the stack
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
}
所以当页面从堆栈中删除时我需要清除它。我怎么知道什么时候会发生这种情况?我能找到的唯一方法是在主页上监听主导航页面上的事件。
我还创建了一个接口(interface) IPageDispose 并在我的页面上实现它。
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new Pages.MainPage());
if (MainPage is NavigationPage page)
{
page.Popped += Page_Popped;
page.PoppedToRoot += Page_PoppedToRoot;
}
}
/// <summary>
/// https://www.johankarlsson.net/2017/08/popped-pages-in-xamarin-forms.html
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Page_PoppedToRoot(object sender, NavigationEventArgs e)
{
if (e is PoppedToRootEventArgs args)
{
foreach(Page page in args.PoppedPages)
Page_Popped(sender, new NavigationEventArgs(page));
}
}
private void Page_Popped(object sender, NavigationEventArgs e)
{
if (e.Page is IPageDispose ipd)
{
ipd.Dispose();
}
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
public async static void HandleError(Exception ex)
{
Logger.Entry(ex);
await Application.Current.MainPage.Navigation.PushModalAsync(new Pages.LogPages.LogPage(ex));
}
}
}
namespace StoreTrak.Pages
{
public interface IPageDispose
{
void Dispose();
}
}
public partial class TestPage : ContentPage, IPageDispose
{
public TestPage()
{
InitializeComponent();
}
public void Dispose()
{
// How do I know which control to clear?
// give each one a name and hardcode the Clear method?
throw new NotImplementedException();
}
现在,我如何知道要清除哪些控件?我做了这个复杂的过程,仍然需要命名控件并在后面的代码中跟踪它们?这比在后面的代码中使用事件监听器更好吗?
public class _BasePage : ContentPage
{
protected void IntegerValidation_TextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrEmpty(e.NewTextValue))
{
((Entry)sender).Text = "0";
return;
}
if (!int.TryParse(e.NewTextValue, out int x))
((Entry)sender).Text = e.OldTextValue;
}
}
}
最佳答案
感谢您更新问题。
关于OnDetachingFrom方法,我们可以看一下这个官方文档。
The
OnDetachingFrom
method is fired when a behavior is removed from a control, and is used to perform any required cleanup such as unsubscribing from an event to prevent a memory leak. However, behaviors are not implicitly removed from controls unless the control's Behaviors collection is modified by aRemove
orClear
method.
我们会看到,除非调用Remove
和Clear
方法,否则OnDetachingFrom
通常不会被触发。
But where do I call it? I can't put it in OnApprearing because it can be called when a new page is navigated to, then when this page is shown again the behaviors are gone.
我们可以在页面的OnDisappearing
方法上调用clear
方法,但是还需要在进入页面时添加行为。
例如:
protected override void OnAppearing()
{
base.OnAppearing();
myentry.Behaviors.Add(new NumericValidationBehavior());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
myentry.Behaviors.Clear();
}
=================================更新=============== =======================
您可以使用Style 和 Trigger 对于 Entry
,则无需通过编码为每个 Entry
添加/删除行为。
创建一个NumericValidationBehavior
类:
public class NumericValidationBehavior : Behavior<Entry>
{
public static readonly BindableProperty AttachBehaviorProperty =
BindableProperty.CreateAttached ("AttachBehavior", typeof(bool), typeof(NumericValidationBehavior), false, propertyChanged: OnAttachBehaviorChanged);
public static bool GetAttachBehavior (BindableObject view)
{
return (bool)view.GetValue (AttachBehaviorProperty);
}
public static void SetAttachBehavior (BindableObject view, bool value)
{
view.SetValue (AttachBehaviorProperty, value);
}
static void OnAttachBehaviorChanged (BindableObject view, object oldValue, object newValue)
{
var entry = view as Entry;
if (entry == null) {
return;
}
bool attachBehavior = (bool)newValue;
if (attachBehavior) {
entry.Behaviors.Add (new NumericValidationBehavior ());
} else {
var toRemove = entry.Behaviors.FirstOrDefault (b => b is NumericValidationBehavior);
if (toRemove != null) {
entry.Behaviors.Remove (toRemove);
}
}
}
protected override void OnAttachedTo (Entry entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo (entry);
}
protected override void OnDetachingFrom (Entry entry)
{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom (entry);
}
void OnEntryTextChanged (object sender, TextChangedEventArgs args)
{
double result;
bool isValid = double.TryParse (args.NewTextValue, out result);
((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
}
}
然后在ContentPage.Xaml中:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:WorkingWithBehaviors;assembly=NumericValidationBehaviorStyle" x:Class="WorkingWithBehaviors.NumericValidationPage" Title="XAML" IconImageSource="xaml.png">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused"
Value="True">
<Setter Property="local:NumericValidationBehavior.AttachBehavior"
Value="true" />
<!-- multiple Setters elements are allowed -->
</Trigger>
<Trigger TargetType="Entry"
Property="IsFocused"
Value="False">
<Setter Property="local:NumericValidationBehavior.AttachBehavior"
Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Padding="10,50,10,0">
<Label Text="Red when the number isn't valid" FontSize="Small" />
<Entry Placeholder="Enter a System.Double" />
</StackLayout>
</ContentPage>
关于c# - XAML 框架从未针对表单行为调用 OnDetachingForm,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63766598/