我的应用程序的主页面上有一个标签,应该是每十五秒更新一次,但它只更新一次,之后,很多事情都停止工作。例如,如果我在标签更新后尝试打开一个新页面,页面的标题就会被画在与后退按钮相同的位置(这两个按钮都是Xamarin在工具栏中生成的),而且页面的内容根本无法加载。另外,我在页面上有一个ListView,如果我尝试选择一个项目(应该是打开一个新的页面),只有第一次能成功,之后ListView就消失了,但是选中的项目后面出现的橙色框框却一直在那里。
目前标签的工作原理是,我在App类中有一个计时器,它从一个列表中随机选择一段文本,我在App的OnStart()函数中加载了这段文本(这部分工作正常),然后启动一个应该更新标签的事件。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Timers;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Partylist.Views;
using Partylist.Models;
namespace Partylist
{
public partial class App : Application, INotifyPropertyChanged
{
// Variable to store the currently selected event.
public static Event selectedEvent;
// Variable to store the currently selected list.
public static PartylistList selectedList;
// Struct to store information about tips.
public struct Tip
{
// A short version of the tip for the banner at the bottom of the screen.
public string Summary { get; set; }
// The full tip, which you can read by clicking the "More" button in the banner.
public string Full { get; set; }
}
// Array of tips.
public List<Tip> tips = new List<Tip>();
// Current tip.
public Tip CurrentTip { get; set; }
// Timer that gets the tip to update.
public Timer tipTimer = new Timer(15000);
// Random number generator for choosing the tip.
public Random rand = new Random();
// Event that tells the tip banners on the pages to update.
public static event EventHandler TipUpdate;
// Constructor.
public App()
{
// Do whatever initialization stuff this does.
InitializeComponent();
// Subscribes the timer's event handling function to its event.
tipTimer.Elapsed += OnTimerElapsed;
// Open the first page: the list of events.
MainPage = new NavigationPage(new EventsPage()) {
BarTextColor = Color.FromHex("FF4081")
};
}
// Loads tips data.
private void LoadTips()
{
// Variable for the assembly.
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(App)).Assembly;
// Variable for the stream I use to read the text file.
Stream tipsStream = assembly.GetManifestResourceStream("Partylist.Resources.tips.txt");
// And a variable for the StreamReader.
StreamReader tipsReader = new StreamReader(tipsStream);
// Read the whole file into the list of tips.
while (!tipsReader.EndOfStream)
{
// Read a line into a "sumamry" variable.
string sum = tipsReader.ReadLine();
// Read another line into a "full" variable.
string full = tipsReader.ReadLine();
// Add an item to the list of tips that uses "summary" as the summary
// and "full" as the full tip.
tips.Add(new Tip()
{
Summary = sum,
Full = full
});
}
// Random index of the chosen tip.
int index = rand.Next(tips.Count);
// Set the current tip as the tip at that index.
CurrentTip = tips.ElementAt(index);
// Start timer (if it needs it).
tipTimer.Start();
}
// Event handling function for when the timer goes off.
private void OnTimerElapsed(object source, ElapsedEventArgs e)
{
// Random index of the chosen tip.
int index = rand.Next(tips.Count);
// Set the current tip as the tip at that index.
CurrentTip = tips.ElementAt(index);
// Fire the event to update the pages' tip banners.
TipUpdate?.Invoke(this, e);
}
// Standard lifecycle events.
protected override void OnStart()
{
// Call a function that loads the tips.
LoadTips();
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
}
在页面的OnAppearing()方法中,我将标签的文本设置为当前的提示(此时是空的),并将更新标签的函数订阅到定时器发射的事件中。
using Partylist.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Partylist.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventsPage : ContentPage
{
// Text of the tip banner.
public string BannerText { get; set; }
// List of events, used to populate
// the page's ListView (see the XAML).
public ObservableCollection<Event> EventList { get; set; }
// Constructor.
public EventsPage()
{
// Does all the stuff to make the page
// exist that doesn't involve anything
// specific to this particular page in
// this particular app.
InitializeComponent();
// Set the label's BindingContext to the
// App class so it can update its text.
tipLabel.BindingContext = (App)App.Current;
}
// Runs when the page appears.
protected override void OnAppearing()
{
// Call the regular OnAppearing method.
base.OnAppearing();
// Set the BindingContext of the page to itself.
BindingContext = this;
// Update the ListView.
UpdateListView();
// Set the banner's text to the current tip's sumamry.
tipLabel.Text = ((App)App.Current).CurrentTip.Summary;
OnPropertyChanged("CurrentTip");
// Subscribe the OnTipUpdate function to the tipUpdate event in the app
// class.
App.TipUpdate += OnTipUpdate;
}
// Function to update the ListView whent he page loads or when something changes.
private void UpdateListView()
{
// Set the EventList to a new ObservableCollection
// which will be populated.
EventList = new ObservableCollection<Event>();
// Loop to populate the ObservableCollection.
for (int i = 0; i < Directory.GetDirectories(
Environment.GetFolderPath(
Environment.SpecialFolder
.LocalApplicationData))
.Length; i++)
{
// Add a new event.
EventList.Add(new Event()
{
// Set the folder name to the name of the folder
// that the even corresponds to.
FolderName = new DirectoryInfo(Directory.GetDirectories(
Environment.GetFolderPath(
Environment.SpecialFolder
.LocalApplicationData))[i]).Name,
// Sets the date/time created to the folder's
// creation date.
DateCreated = Directory
.GetCreationTime(Directory.GetDirectories(
Environment.GetFolderPath(
Environment.SpecialFolder
.LocalApplicationData))[i]),
// Sets the date/time last edited to the
// folder's write date.
DateEdited = Directory
.GetLastWriteTime(Directory.GetDirectories(
Environment.GetFolderPath(
Environment.SpecialFolder
.LocalApplicationData))[i])
});
// Set the ItemsSource of the ListView in the
// XAML to the ObservableCollection.
EventsListView.ItemsSource = EventList;
// Calls OnPropertyChanged() which makes the ListView update.
OnPropertyChanged("EventList");
}
}
// Function to go to the "New Event" page.
async void OnNewEventClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new NewEventPage());
}
// Function for when a ListView item is selected.
async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
App.selectedEvent = (Event)e.SelectedItem;
await Navigation.PushAsync(new ListsPage());
}
// Function to delete an event if the "Delete" context action is selected.
async void OnDelete(object sender, EventArgs e)
{
// Represents the thing to be deleted.
var del = (MenuItem)sender;
// Displays a confirmnation popup and stores the user's answer in a variable.
var answer = await DisplayAlert("Delete this event?",
"Are you sure you want to delete the event: \"" +
((Event)del.CommandParameter).FolderName + "\"?", "Delete", "Cancel");
// If the user accepted, delete the event with the MenuItem that ran this function.
if (answer)
{
Directory.Delete(Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData),
((Event)del.CommandParameter).FolderName), true);
// Set the ItemsSource to null and back to make the ListView update.
EventsListView.ItemsSource = null;
UpdateListView();
}
}
// Function for when the current tip updates.
public void OnTipUpdate(object sender, EventArgs e)
{
// Make the label's text update.
tipLabel.Text = ((App)App.Current).CurrentTip.Summary;
OnPropertyChanged("CurrentTip");
}
}
}
另外,这里是页面的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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Partylist.Views.EventsPage"
Title="Events"
BackgroundColor="White">
<ContentPage.ToolbarItems>
<ToolbarItem IconImageSource="settings_gear.png"
Priority="0"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<!--Main layout of the page-->
<StackLayout>
<!--ListView of the events-->
<ListView x:Name="EventsListView"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!--These contewxt actions are buttons that appear
when you long press the item (Android) or swipe
left (iOS).-->
<ViewCell.ContextActions>
<MenuItem Clicked="OnDelete"
CommandParameter="{Binding .}"
Text="Delete"
IsDestructive="true"/>
</ViewCell.ContextActions>
<!--This is the content that actually appears-->
<StackLayout Padding="20,5">
<Label Text="{Binding FolderName}"
TextColor="#FF7700"
FontSize="Large"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--"New Event" button-->
<Button Text="+ Add New Event"
TextColor="#ff418b"
FontSize="Large"
BackgroundColor="#00ffffff"
Clicked="OnNewEventClicked"/>
<!--The banner at the bottom of the screen that gives tips-->
<Frame BorderColor="#ff418b"
Padding="0">
<FlexLayout Direction="Row"
AlignItems="Stretch"
JustifyContent="SpaceBetween">
<!--The "Tip" icon-->
<Image Source="tip_icon.png"
Margin="10"
FlexLayout.Basis="50"/>
<!--The short version of the tip-->
<Label x:Name="tipLabel"
VerticalTextAlignment="Center"
TextColor="#bb0099"
FontSize="Medium"
FontAttributes="Bold"
FlexLayout.Basis="250"/>
<!--The button that opens up a screen
with tyhe rest of the tip-->
<Button Text="More"
TextColor="White"
FontAttributes="Bold"
FontSize="Medium"
BackgroundColor="#ff418b"
FlexLayout.Basis="100"/>
</FlexLayout>
</Frame>
</StackLayout>
</ContentPage.Content>
</ContentPage>
我到底做错了什么,我怎么才能让我的应用在标签更新时不崩溃?
你需要更新 Main thread
:
Device.BeginInvokeOnMainThread (() => {
label.Text = "Async operation completed";
});