For my first project during learning xamarin I make simple app where user can create a note and add alarm time to schedule local notification.
I have a problem when app resume from background.
To the point.
Note model:
public class Note
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[MaxLength(255)]
public string Title { get; set; }
[MaxLength(255)]
public string Content { get; set; }
public DateTime TimeCreate { get; set; }
public DateTime AlarmTime { get; set; }
public bool AlarmTimeActive { get; set; }
}
In main page there is list of notes. Every note has a switch button where user can on/off time alarm.
If user try to switch on alarm, function check is schedule time gone or not. If gone then switch stay in off position and app display information. In other case function updates value in data base to "true".
XAML
<local:ButtonActiveSwitcher Toggled="Switch_Toggled" IsToggled="{Binding AlarmTimeActive}" Active="{Binding .}" />
function "Switch_Toggled"
private void Switch_Toggled(object sender, ToggledEventArgs e)
{
var switchBtn = sender as Switch;
var item = ((ButtonActiveSwitcher)sender).Active;
if (item != null)
{
if (item.AlarmTime < DateTime.Now)
{
if (_nooLoopTime.AddSeconds(2) < DateTime.Now) //Prevent double display alert
{
DisplayAlert("ALERT", "Time gone", "OK");
_nooLoopTime = DateTime.Now;
}
switchBtn.IsToggled = false;
return;
}
DataBaseService.updateRecord(item);
}
}
And this function works fine when user tapped switcher.
Next point.
In MainPage.cs in function OnAppearing app fired function DataBaseService.checkNoteAlarmTimeActive();. In this function app check AlarmTime in notes. If AlarmTimeActive is active but schedule time has gone then change AlarmTimeActive to "false".
First app checks Notes in DB and update them, next function loadNotes() getting Notes from DB and populate list.
So before app gets Notes from DB first update records in DB.
MainPage.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
private Sorting _sorting;
private int _sortOption;
private bool _activeSwitcherState;
private DateTime _nooLoopTime;
public MainPage(int noteid)
{
DataBaseService.CreateTables();
this._sorting = new Sorting();
InitializeComponent();
this.AddPickerItems();
if (noteid != 0)
{
this.loadNoteView(noteid);
}
}
protected async override void OnAppearing()
{
await DataBaseService.checkNoteAlarmTimeActive();
this.loadNotes();
base.OnAppearing();
}
/// <summary>
/// Generate list of notes basic on current sorting option and active switcher
/// </summary>
private async void loadNotes()
{
listNotes.ItemsSource = await _sorting.sortNotes(_sortOption, _activeSwitcherState);
}
}
And here is my problem.
For example: one Note has AlarmTimeActive "true" and user tapped "Home" button, app goes to background. Later when schedule alarm time has gone user put app to foreground by tapping app from list under App Switcher button. And for some reason app first display alert "Time gone" and latter (I think) do function OnAppearing(). Finally in main page I have a list of Notes with updated records, but why app displays this alert first?
But this problem doesn't appear in three other cases.
User kill app in App Switcher list and open again tapping icon in application list.
User exit from app tapped Back Button.
User resume app by tapping notification.
So why if user resume app from App Switcher list, this alert is displayed but in other cases not?
I hope my description is clear.
Please explain to me why it happens and how to fix it.
Try to avoid async void except for event handlers. OnAppearing is not an event handler. but is it called before the actual Appearing event, which gives you a chance to subscribe to it with an actual event handler that would allow you to use async/await correctly.
protected override void OnAppearing() {
this.Appearing += Page_Appearing;
base.OnAppearing();
}
private async void Page_Appearing(object sender, EventArgs e) {
//...call async code here
await DataBaseService.checkNoteAlarmTimeActive();
var notes = await loadNotes();
listNotes.ItemsSource = notes;
//unsubscribing from the event (optional but advised)
this.Appearing -= Page_Appearing;
}
/// <summary>
/// Generate list of notes basic on current sorting option and active switcher
/// </summary>
private Task<IEnumerable<Note>> loadNotes()
{
return _sorting.sortNotes(_sortOption, _activeSwitcherState);
}
I would probably guess that this piece of code...
if (noteid != 0)
{
this.loadNoteView(noteid);
}
called in the constructor should be refactored out into the event handler as well.
Related
I'm building a Blazor server app that has a side navigation component as well as a top navigation component.
I want the side nav's "selected" status to get cleared if the user selects an item from the top nav menu.
In my MainLayout.blazor.cs I have a variable called ResetSelection:
protected bool ResetSelection { get; set; } = false;
I'm passing this from the MainLayout.blazor file into both top and side nav components:
<SideNav AuthUser="#navAuth.User.GetAuthUserModel()" ResetSelection="#ResetSelection" />
<TopNav AuthUser="#Auth.User.GetAuthUserModel()" ResetSelection="#ResetSelection" />
In TopNav.razor.cs I check if a nav item has been selected, and if it has, I set the variable to true:
private void itemSelected(MenuEventArgs<CategoryModel> args)
{
// if item selected set the main nav selected item to null
// breakpoint gets hit-- this method gets fired as expected
ResetSelection = true;
}
In the SideNav.razor.cs component I use an OnParameterSet to check if the param is true, and if so I clear the current selected nav item and reset the variable to false:
protected override void OnParametersSet()
{
base.OnParametersSet();
if (ResetSelection == true)
{
// we never get here!
NavMenu.UnselectAll();
ResetSelection = false;
}
}
I don't ever get the OnParametersSet triggered with a ResetSelection == true condition-- and I don't understand why. I can't seem to make this interaction work between two child components.
Is the parameter passed in being scoped to the local component when it has its value changed in TopNav.razor.cs?
You can apply the Blazor Notification Pattern to your problem.
This approach gets away from the fragile spaghetti plumbing inevitable when you try and create more than simple Parameter/Callback relationships between parent/child components.
Create a simple State object and then cascade it. readonly and IsFixed prevents RenderTree cascades: renders are driven by events.
State Object:
public class MenuState
{
public event EventHandler<EventArgs>? StateChanged;
public void NotifyStateCahnged(object? sender)
=> this.StateChanged?.Invoke(sender, EventArgs.Empty);
}
Your layout.
<CascadingValue Value="_state" IsFixed>
//... layout markup
</CascadingValue>
#code {
private readonly MenuState _state = new();
}
Then wherever you use it:
#implements IDisposable
<h3>TopMenu</h3>
#code {
[CascadingParameter] private MenuState State { get; set; } = new();
protected override void OnInitialized()
=> this.State.StateChanged += this.OnStateChanged;
protected async Task MenuClicked()
{
// do whatever maybe async
// emulate an async action
await Task.Delay(10);
this.State.NotifyStateCahnged(this);
}
private void OnStateChanged(object? sender, EventArgs e)
{
// only need to render if it wasn't me who triggered the event
if (sender != this)
{
// Do whatever
this.StateHasChanged();
}
}
public void Dispose()
=> this.State.StateChanged -= this.OnStateChanged;
}
An alternative to the cascade is to register MenuState as a Scoped Service and then inject it where you need it.
I have a DataGridView which has a Button column.
Each record in the DataGridView will have a separate Button. A QueueMusic object containing Row-specific data should be queued upon clicking a Row's Button.
I currently have this working by placing a custom class (QueueMusic) onto a Queue collection using that collection's .Enqueue() method.
I have two event handler methods.
DataGridViewAllMusic_SelectionChanged method, which begins playing music associated the current Button's Row.
DataGridViewAllMusic_CellClick method, which handles queueing the playlist (defined within the QueueMusic class) that is associated with the current Button's Row.
The Problem
Once music is playing, each subsequent click of a Row's Button interrupts the currently playing music with music defined by the newest Row's QueueMusic object.
I have a class QueueMusic.
internal class QueueMusic
{
public string Url { get; set; }
public int RowIndex { get; set; }
public static Queue<QueueMusic> queulist = new Queue<QueueMusic>();
}
and a DataGridView CellClickEvent
private void DataGridViewAllMusic_CellClick(object sender, DataGridViewCellEventArgs
e)
{
var senderGrid = (DataGridView)sender;
if (senderGrid.Columns[e.ColumnIndex] is DataGridViewButtonColumn && e.RowIndex >=
0)
{
QueueMusic qm = new QueueMusic();
qm.RowIndex = e.RowIndex;
qm.Url = DataGridViewAllMusic.Rows[e.RowIndex].Cells[2].Value.ToString();
QueueMusic.queulist.Enqueue(qm);
}
}
private void DataGridViewAllMusic_SelectionChanged(object sender, EventArgs e)
{
play();
}
Desired Behavior
How can I prevent a Row's Button click event from being handled by DataGridViewAllMusic_SelectionChanged when music is already playing?
The desired behavior is to queue the next playlist in the background without inturrupting the currently playing playlist.
You omitted several crucial parts of your code when writing your post here.
You need to include the play() method's logic. Understanding what is happening there will greatly help us help you.
For example, what data comes from the URL that you inject into each instance of QueueMusic?
You'll definitely need to define a Form-level property to hold a single instance of whatever Queue or Music class you have. Once instantiated on Form load, that object should only be added to when you click a Row's Button. You'll need to handle Dequeueing playlists as they finish; maybe that's handled by other code that you didn't show.
The static Queue that you have defined inside of QueueMusic is not a good design.
Music playlists start over each time you click because you are overwriting the QueueMusic object on each click.
I would suggest something like the following:
internal class MusicQueue
{
public MusicQueue()
{
PlaylistQueue = new Queue<Playlist>();
// any other setup...
}
public Queue<Playlist> Playlist { get; set; }
}
internal class Playlist
{
public string Url { get; set; }
public int RowIndex { get; set; }
public void Play()
{
// ?
}
public bool IsPlaying { get; set; }
}
Within your Form's main class code, do the following:
...
private MusicQueue queue { get; set; }
private void Form1_Load(object sender, EventArgs e)
{
queue = new MusicQueue();
// any other setup...
}
...
Then within the two handlers:
private void DataGridViewAllMusic_CellClick(object sender, DataGridViewCellEventArgs e)
{
...
// get data from the DataGridView's Row
// put the new playlist on the queue
// you may want to check if the current Row's playlist is already on the queue
queue.Playlist.Add(new Playlist() { (construct the new playlist here) };
}
private void DataGridViewAllMusic_SelectionChanged(object sender, EventArgs e)
{
// depends on what `play()` is doing, but I'd probably
// suggest checking if any playlist is playing.
// if not, start the first playlist
if (queue.Playlist.Any(p => p.IsPlaying)
{
queue.Playlist.FirstOrDefault().Play();
}
}
how are you?
I created a top ContentPage class inheriting a ZXingScannerPage class to be able to create methods in order to better streamline the code. And in it a method that takes as a parameter a Label and a ContentPage to set the data read by the Scanner.
public String ScannResult { get; set; }
}
public class ScannerHelper : ZXingScannerPage
{
public ScannerHelper()
{
this.OnScanResult += (result) => {
MessagingCenter.Send( new Message { ScannResult = result.Text }, "ScannResult");
// Parar de escanear
this.IsScanning = false;
// Alert com o código escaneado
Device.BeginInvokeOnMainThread(() => {
DisplayAlert("Código escaneado", result.Text, "OK");
Navigation.PopAsync(false);
});
};
}
public static void teste(Label label, ContentPage contentPage)
{
MessagingCenter.Subscribe<Message>(contentPage, "ScannResult", message => {
label.Text = message.ScannResult;
});
}
}
}
So far everything is working, but when I call this class on a button and run it later, the screen takes the data and goes to the label, but when going to another page with a PushAsync the page is blank and if I go back to the pages and try to go to the next one it is blank and the strangest thing if I leave the app on my cell phone going to the menu and returning to it the page is normal. Example: Page A calls the Scanner and works, on Page A I go to Page B and it is blank, I go back to Page A and I go back to the page before Page A and then I go back to A to A and now it is in White.
Example of how I use the Methods on the buttons and on the page:
{
InitializeComponent();
ScannerHelper.teste(lblNomeProduto, this);
}
private void ButtonScanner(object sender, EventArgs e)
{
Navigation.PushAsync(new ScannerHelper());
}
I have a problem. I created a Database with a table based on the following class:
public class Device
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
In my App.xaml.cs I do the following:
static List<Device> knownDeviceList;
public App()
{
InitializeComponent();
MyHandler();
MainPage = new NavigationPage(new Page1());
}
public Task MyHandler()
{
return GetKnownDevices();
}
private async Task Get()
{
KnownDeviceList = deviceDatabaseController.GetDevice();
}
public static List<KnownDevice> KnownDeviceList
{
get
{
if (knownDeviceList == null)
{
knownDeviceList = new List<KnownDevice>();
}
return knownDeviceList;
}
set
{
knownDeviceList = value;
}
}
After that code automatically runs, I have a filled KnownDeviceList.
Now in Page1 I made a ListView with a ViewModel that shows the devices from KnownDeviceList using bindings. On that same page I made a button to add a new device, so it takes you to Page2 using a NavigationPage. But when I write the new device to the database, I use: Navigation.PopToRootAsync(); to go back, but now I want the ListView to refresh and KnownDeviceList to get the devices from the database again, so my new device is in the ListView.
How can I achieve this, because I have no idea what to do after I added it!?
I problem here is that you are changing the collection. But not notifying the UI about it.
List<> will not notify the collection change on its own. Where as ObservableCollection does notify collection changes, like Add, Remove, etc. Hence the name ObservableCollection.
Using ObservableCollection<KnownDevice> instead of List<KnownDevice> could solve this issue.
This is my first C# application, entirely self-taught without any prior software programming background. I did some research on Undo/Redo but could not find anything helpful (or easy to understand). Therefore, I'm hoping someone can help me in designing undo/redo function for my program (winforms application). The application consist of a main form where subsequent child forms will be called to record user specified values during certain events (button clicks etc). After every event is handled, a bitmap will be drawn in buffer and then loaded to a picturebox within the main form during the OnPaint event of the main form. Each input in separated into custom class objects and added into separate List and BindingList. Objects contained within List are used for graphics (to indicate coordinates etc) while objects in BindingList are used to display some important values on DataGridView. Just to give a brief description, the codes look something like this:
public partial class MainForm : form
{
public class DataClass_1
{
public double a { get; set; }
public double b { get; set; }
public SubDataClass_1 { get; set; }
}
public class SubDataClass_1
{
public double x { get; set; }
public double y { get; set; }
public string SomeString { get; set; }
public CustomEnum Enum_SubDataClass_1 { get; set; }
}
public class DisplayDataClass
{
public string SomeString { get; set; }
public double e { get; set; }
public double f { get; set; }
}
public enum CustomEnum { Enum1, Enum2, Enum3 };
// Lists that contain objects which hold the necessary values to be drawn and displayed
public List<DataClass_1> List_1 = new List<DataClass_1>();
public List<DataClass_2> List_2 = new List<DataClass_2>(); // Object has similar data types as DataClass_1
public BindingList<DisplayDataClass> DisplayList = new BindingList<DisplayDataClass>();
Bitmap buffer;
public MainForm()
{
InitializeComponent();
dgv.DataSource = DisplayList;
}
private void DrawObject_1()
{
// some drawing codes here
}
private void DrawObject_2()
{
// some drawing codes here
}
protected override void OnPaint(PaintEventArgs e)
{
DrawObject_1();
DrawObject_2();
pictureBox1.Image = buffer;
}
// Event to get input
private void action_button_click(object sender, EventArgs e)
{
ChildForm form = new ChildForm(this);
form.ShowDialog();
Invalidate();
}
}
The child forms' codes look something like this:
public partial class ChildForm : form
{
public ChildForm(MainForm MainForm)
{
InitializeComponent();
// Do something
}
private void ok_button_click(object sender, EventArgs e)
{
DataClass_1 Data_1 = new DataClass_1();
DisplayDataClass DisplayData = new DisplayDataClass();
// Parsing, calculations, set values to Data_1 and DisplayData
MainForm.List_1.Add(Data_1);
MainForm.DisplayList.Add(DisplayData);
this.DialogResult = System.Windows.Forms.DialogResult.OK;
this.Close();
}
}
Since all necessary data are stored in the lists and will only be changed after certain events are triggered (mostly button clicks), therefore I tried to use these lists to determine the state of the application during run time. My approach in implementing the undo/redo function is by adding the following codes:
public partial class MainForm : form
{
public class State()
{
public List<DataClass_1> List_1 { get; set; }
public List<DataClass_2> List_2 { get; set; }
public BindingList<DisplayDataClass> DisplayList { get; set; }
// and so on
public State()
{
List_1 = new List<DataClass_1>();
List_2 = new List<DataClass_2>();
DisplayList = new BindingList<DisplayDataClass>();
}
}
State currentState = new State();
Stack<State> undoStack = new Stack<State>();
Stack<State> redoStack = new Stack<State>();
private void MainForm_Shown(object sender, EventArgs e)
{
// Saves original state as first item in undoStack
undoStack.Push(currentState);
}
protected override void OnPaint(PaintEventArgs e)
{
// Update lists from currentState before drawing
List_1 = new List<DataClass_1>(currentState.List_1);
List_2 = new List<DataClass_2>(currentState.List_2);
DisplayList = new BindingList<DisplayDataClass>(currentState.DisplayList);
}
// When undo button is clicked
private void undo_button_Click(object sender, EventArgs e)
{
if (undoStack.Count > 0)
{
redoStack.Push(currentState);
undoStack.Pop();
currentState = undoStack.Last();
Invalidate();
}
}
// When redo button is clicked
private void redo_button_Click(object sender, EventArgs e)
{
// Have not thought about this yet, trying to get undo done first
}
// Events that trigger changes to values held by data objects
private void action_button_Click(object sender, EventArgs e)
{
// Replace the following code with previously stated version
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ChildForm form = new ChildForm(this)
UpdateState();
undoStack.Push(currentState);
Invalidate();
}
}
// To update currentState to current values
private void UpdateState()
{
currentState.List_1 = new List<DataClass_1>(List_1);
currentState.List_2 = new List<DataClass_2>(List_2);
currentState.DisplayList = new BindingList<DisplayDataClass>(DisplayList);
// and so on
}
}
Result:
The application does not perform the undo function correctly. The program shows the correct output under normal conditions but when the undo event is triggered, regardless of how many objects have been drawn, the application reverts back to initial state (the state where there is no recorded data). I've used System.Diagnostics.Debug.WriteLine() during events where the stack is changed to check the number of counts within undoStack and it seems to give the correct counts. I'm guessing that the lists need to be copied/cloned in a different manner? Or am I doing something wrong here? Can anyone please guide me? Performance, readability, resource management, future maintenance and etc need not be considered.
There are a lot of approaches that will work, each with different strengths and weaknesses, but I generally like to define an abstract Action class and then a separate UndoRedoStack class.
The Action class would have two methods (Do and Undo) which each subclassed Action can implement. You isolate any logic that can "change state" to these Action subclasses thereby keeping that logic neatly encapsulated.
The UndoRedoStack is like a regular stack except with three core methods.
ApplyAction (like Push)
UndoAction (like Pop, but be sure to only
move the pointer/index without truncating or throwing away any
existing actions).
RedoAction (like Push, but you use the next value
already in the underlying stack/list instead of pushping/inserting a
new one).
Usually I find the biggest challenge then becomes designing each Action subclass in such a way that it maintains enough information to both undo and redo itself. But being able to encapsulate all state manipulation logic to individual Action subclasses usually makes it easiest for me to maintain in the long run.
You are storing reference objects in your stacks. If you want your method to work, you need to implement a clone() method in your state object, and store a new clone each time, otherwise, changes made are made to each member of the stack, as they all point to the same reference object.