List of Stopwatches - restarting begins all timers due to binding - c#

Hope you can help ... I have a list of activities which use a stopwatch to track each one of their activity durations. When I tap an activity in my list, it starts the stopwatch for the activity tapped and when I tap the activity row again it stops the stopwatch and resets it.
However when I then tap another activity, although it is really only updating the duration for the activity tapped in the backend, the frontend UI updates all of the activities timers with the same activity time tapped because they are binded to the same activityduration element. I don't know how to only change the activity duration for the activity tapped in the front end. Could anyone advise on how to do this without re-structuring my model/collection? Or will I have to make a subnest for the activity name in my collection?
On tap event in code behind:-
public async void OnActivityTap(object sender, EventArgs e)
{
var item = (ViewCell)sender;
UserActivities.Activities myactivitiesModel = item.BindingContext as UserActivities.Activities;
if (myactivitiesModel == null)
{return;}
// OnPropertyChanged("ActivityDuration");
// mystopwatch.Reset();
//ViewModel.getUserActivities();
foreach (var x in ViewModel.UserActivitiesList) {
if(x.ActivityName == myactivitiesModel.ActivityName) {
int seconds = 1;
//if the activity is not enabled and the activity is tapped
if (myactivitiesModel.ActivityEnabled == false)
{
//enable the activity and start the stopwatch
myactivitiesModel.ActivityEnabled = true;
//Get the current duration and add it onto the stopwatch start time
ts = x.ActivityDuration;
//Reset the stopwatch back to zero
mystopwatch = new Stopwatch();
//Start the stopwatch
mystopwatch.Reset();
mystopwatch.Start();
while (myactivitiesModel.ActivityEnabled == true)
{
Device.StartTimer(TimeSpan.FromSeconds(seconds), () =>
{
myactivitiesModel.ActivityDuration = (mystopwatch.Elapsed + ts);
return true;
});
return;
}
}
else if (myactivitiesModel.ActivityEnabled == true)
{
//disable the activity and stop the stopwatch
x.ActivityEnabled = false;
//Stop the clock
mystopwatch.Stop();
//Store activity time stopped
await MongoService.UpdateUserActivityTime(userIdentity, myactivitiesModel.ActivityName, x.ActivityDuration);
//Store the time stopped in the UserActivitiesList binded to the UI list
x.ActivityDuration = myactivitiesModel.ActivityDuration;
OnPropertyChanged("ActivityDuration");
return;
}
} //end of if activityName Tapped
} //end of foreach
} //end of OnTap Activity
Model:
public class UserActivities : INotifyPropertyChanged
{
[BsonId, BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
string _foreignUserID;
[BsonElement("foreignUserID")]
public string foreignUserID
{
get => _foreignUserID; set
{
if (_foreignUserID == value)
return;
_foreignUserID = value;
HandlePropertyChanged();
}
}
[BsonElement("activities")]
public ObservableCollection<Activities> UserTimedActivities { get; set; }
public class Activities : INotifyPropertyChanged
{
string _activityName;
[BsonElement("activityName")]
public string ActivityName
{
get => _activityName; set
{
if (_activityName == value)
return;
_activityName = value;
HandlePropertyChanged();
}
}
TimeSpan _activityDuration;
[BsonElement("activityDuration")]
public TimeSpan ActivityDuration
{
get => _activityDuration; set
{
if (_activityDuration == value)
return;
_activityDuration = value;
HandlePropertyChanged();
}
}
TimeSpan _activityGoalDuration;
[BsonElement("activityGoalDuration")]
public TimeSpan ActivityGoalDuration
{
get => _activityGoalDuration; set
{
if (_activityGoalDuration == value)
return;
_activityGoalDuration = value;
HandlePropertyChanged();
}
}
Boolean _activityEnabled;
[BsonElement("activityEnabled")]
public Boolean ActivityEnabled
{
get => _activityEnabled; set
{
if (_activityEnabled == value)
return;
_activityEnabled = value;
HandlePropertyChanged();
}
}
public Activities(string activityname, TimeSpan activityduration, TimeSpan activitygoalduration, Boolean activityenabled ) {
ActivityName = activityname;
ActivityDuration = activityduration;
ActivityGoalDuration = activitygoalduration;
ActivityEnabled = activityenabled;
}
void HandlePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public UserActivities(ObservableCollection<Activities> usertimedactivities)
{
UserTimedActivities = usertimedactivities;
}
public event PropertyChangedEventHandler PropertyChanged;
void HandlePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel method to compile list-
public async void getUserActivities()
{
var userActivities = await MongoService.GetUserActivityData(userIdentity);
try
{
if (IsBusy)
return;
IsBusy = true;
UserActivitiesList.Clear();
foreach (var x in userActivities)
{
foreach(var y in x.UserTimedActivities) {
//foreach (var y in x.userActivities)
UserActivitiesList.Add(y);
}
}
}
catch (Exception ex)
{
IsBusy = false;
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
2. Alternative Timer method suggested by Jason (doesn't get elapsed time or update UI):
public async void OnActivityTap(object sender, EventArgs e)
{
var item = (ViewCell)sender;
UserActivities.Activities myactivitiesModel = item.BindingContext as UserActivities.Activities;
if (myactivitiesModel == null)
{ return; }
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityName == myactivitiesModel.ActivityName)
{
if (x.ActivityEnabled == false)
{
x.ActivityEnabled = true;
timer.Enabled = true;
timer.Start();
timer.Elapsed += OnTimedEvent;
x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
else
{
x.ActivityEnabled = false;
timer.Enabled = false;
timer.Stop();
x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
**3. Alternative approach with stopwatch - does work but doesn't update UI until tapped **
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityName == myactivitiesModel.ActivityName)
{
if (x.ActivityEnabled == false)
{
x.ActivityEnabled = true;
//timer.Enabled = true;
mystopwatch.Restart();
mystopwatch.Start();
x.ActivityDuration = mystopwatch.Elapsed + x.ActivityDuration;
NotifyPropertyChanged("ActivityDuration");
//timer.Elapsed += OnTimedEvent;
//x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}
else
{
x.ActivityEnabled = false;
//timer.Enabled = false;
mystopwatch.Stop();
x.ActivityDuration = mystopwatch.Elapsed + x.ActivityDuration;
NotifyPropertyChanged("ActivityDuration");
//x.ActivityDuration = x.ActivityDuration.Add(new TimeSpan(0, 0, 0, 0, (int)interval)); //new
return;
}

I think you're making this way too complicated. I would create a single timer and do something like this
using System.Timers;
...
// 100ms == .1s
double interval = 100;
// CREATE ONE TIMER FOR ALL ACTIVITIES
Timer timer = new Timer(interval);
timer.Elapsed += UpdateTimers;
timer.Start();
...
private void UpdateTimers(object sender, EventArgs a)
{
// this is psudocode, update for your model
foreach(var a in Activities)
{
// if activity is selected
if (a.Active) {
// update the elapsed time
a.Elapsed = a.Elapsed.Add(new Timespan(0,0,0,0,interval));
}
}
}
Final Solution as implemented by OP
public page()
{
timer.Start();
timer.Elapsed += UpdateTimers;
}
private void UpdateTimers(object sender, EventArgs a)
{
foreach (var x in ViewModel.UserActivitiesList)
{
if (x.ActivityEnabled)
{
x.ActivityDuration = x.ActivityDuration.Add(new
TimeSpan(0, 0, 0, 0, (int)interval));
}
}
}

Related

Multi UI-threading and databinding issues

I'm having issues updating the UI threads. Application is running 1 UI thread for each form, meaning just using SyncronizationContext with the UI thread doesn't work. I'm doing this for looping update performance as well as modal popup possibilities like select value before you can use the form.
How I'm creating it in ApplicationContext:
public AppContext()
{
foreach(var form in activeForms)
{
form.Load += Form_Load;
form.FormClosed += Form_FormClosed;
StartFormInSeparateThread(form);
//form.Show();
}
}
private void StartFormInSeparateThread(Form form)
{
Thread thread = new Thread(() =>
{
Application.Run(form);
});
thread.ApartmentState = ApartmentState.STA;
thread.Start();
}
There are controls on each for that are databound and updating with values from the same databound object. Controls being Labels and DataGridview (bound to a bindinglist).
What would be ideal is having the Bindinglist threadsafe and execute on these multiple UI threads. Found some examples that I attempted like this:
List<SynchronizationContext> listctx = new();
public ThreadSafeBindingList2()
{
//SynchronizationContext ctx = SynchronizationContext.Current;
//listctx.Add(ctx);
}
public void SyncContxt()
{
SynchronizationContext ctx = SynchronizationContext.Current;
listctx.Add(ctx);
}
protected override void OnAddingNew(AddingNewEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseAddingNew(e);
}
else
{
listctx[i].Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseListChanged(e);
}
else
{
listctx[i].Send(delegate
{
BaseListChanged(e);
}, null);
}
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
I'm also using a static class as a data property change hub for all controls so I don't change the databinding source more than once (again due to performance), where I have a background worker "ticking" every 1-3 seconds depending on system load:
private static void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
if (timerStart is false)
{
Thread.Sleep(6000);
timerStart = true;
}
while (DisplayTimerUpdateBGW.CancellationPending is false)
{
//UIThread.Post((object stat) => //Send
//{
threadSleepTimer = OrderList.Where(x => x.Status != OrderOrderlineStatus.Claimed).ToList().Count > 20 ? 2000 : 1000;
if (OrderList.Count > 40)
threadSleepTimer = 3000;
UpdateDisplayTimer();
//}, null);
Thread.Sleep(threadSleepTimer);
}
}
private static void UpdateDisplayTimer()
{
var displayLoopStartTimer = DateTime.Now;
TimeSpan displayLoopEndTimer = new();
Span<int> orderID = CollectionsMarshal.AsSpan(OrderList.Select(x => x.ID).ToList());
for (int i = 0; i < orderID.Length; i++)
{
OrderModel order = OrderList[i];
order.OrderInfo = "Ble";
Span<int> OrderLineID = CollectionsMarshal.AsSpan(order.Orderlines.Select(x => x.Id).ToList());
for (int j = 0; j < OrderLineID.Length; j++)
{
OrderlineModel ol = order.Orderlines[j];
TimeSpan TotalElapsedTime = ol.OrderlineCompletedTimeStamp != null ? (TimeSpan)(ol.OrderlineCompletedTimeStamp - ol.OrderlineReceivedTimeStamp) : DateTime.Now - ol.OrderlineReceivedTimeStamp;
string displaytimerValue = "";
if (ol.OrderlineCompletedTimeStamp == null)
displaytimerValue = TotalElapsedTime.ToString(#"mm\:ss");
else
displaytimerValue = $" {(DateTime.Now - ol.OrderlineCompletedTimeStamp)?.ToString(#"mm\:ss")} \n({TotalElapsedTime.ToString(#"mm\:ss")})";
ol.DisplayTimer = displaytimerValue;
}
}
}
Ideally I want to have the labels and datagridview properties databindings so that I can have INotifyPropertyChanged just updating these relevant properties in all UI threads.
Any help would be appreciated!
One of many ways to look at this is that there's only one display area (albeit which might consist of many screens) and only one element of it can change at any given moment. To my way of thinking, this means that having more than one UI thread can often be self defeating (unless your UI is testing another UI). And since the machine has some finite number of cores, having a very large number of threads (whether of the UI or worker variety) means you can start to have a lot of overhead marshalling the context as threads switch off.
If we wanted to make a Minimal Reproducible Example that has 10 Form objects executing continuous "mock update" tasks in parallel, what we could do instead of the "data property change hub" you mentioned is to implement INotifyPropertyChanged in those form classes with static PropertyChanged event that gets fired when the update occurs. To mock data binding where FormWithLongRunningTask is the binding source, the main form subscribes to the PropertyChanged event and adds a new Record to the BindingList<Record> by identifying the sender and inspecting e to determine which property has changed. In this case, if the property is TimeStamp, the received data is marshalled onto the one-and-only UI thread to display the result in the DataGridView.
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Subscribe to the static event here.
FormWithLongRunningTask.PropertyChanged += onAnyFWLRTPropertyChanged;
// Start up the 10 forms which will do "popcorn" updates.
for (int i = 0; i < 10; i++)
{
new FormWithLongRunningTask { Name = $"Form{i}" }.Show(this);
}
}
private void onAnyFWLRTPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is FormWithLongRunningTask form)
{
BeginInvoke(() =>
{
switch (e.PropertyName)
{
case nameof(FormWithLongRunningTask.TimeStamp):
dataGridViewEx.DataSource.Add(new Record
{
Sender = form.Name,
TimeStamp = form.TimeStamp,
});
break;
default:
break;
}
});
}
}
}
The DataGridView on the main form uses this custom class:
class DataGridViewEx : DataGridView
{
public new BindingList<Record> DataSource { get; } = new BindingList<Record>();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode)
{
base.DataSource = this.DataSource;
AllowUserToAddRows = false;
#region F O R M A T C O L U M N S
DataSource.Add(new Record());
Columns[nameof(Record.Sender)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
var col = Columns[nameof(Record.TimeStamp)];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
col.DefaultCellStyle.Format = "hh:mm:ss tt";
DataSource.Clear();
#endregion F O R M A T C O L U M N S
}
}
protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
{
base.OnCellPainting(e);
if ((e.RowIndex > -1) && (e.RowIndex < DataSource.Count))
{
var record = DataSource[e.RowIndex];
var color = _colors[int.Parse(record.Sender.Replace("Form", string.Empty))];
e.CellStyle.ForeColor = color;
if (e.ColumnIndex > 0)
{
CurrentCell = this[0, e.RowIndex];
}
}
}
Color[] _colors = new Color[]
{
Color.Black, Color.Blue, Color.Green, Color.LightSalmon, Color.SeaGreen,
Color.BlueViolet, Color.DarkCyan, Color.Maroon, Color.Chocolate, Color.DarkKhaki
};
}
class Record
{
public string Sender { get; set; } = string.Empty;
public DateTime TimeStamp { get; set; }
}
The 'other' 10 forms use this class which mocks a binding source like this:
public partial class FormWithLongRunningTask : Form, INotifyPropertyChanged
{
static Random _rando = new Random(8);
public FormWithLongRunningTask() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_ = runRandomDelayLoop();
}
private async Task runRandomDelayLoop()
{
while(true)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(_rando.NextDouble() * 10));
TimeStamp = DateTime.Now;
Text = $"# {TimeStamp.ToLongTimeString()}";
BringToFront();
}
catch (ObjectDisposedException)
{
}
}
}
DateTime _timeStamp = DateTime.Now;
public DateTime TimeStamp
{
get => _timeStamp;
set
{
if (!Equals(_timeStamp, value))
{
_timeStamp = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
{
add => PropertyChanged += value;
remove => PropertyChanged -= value;
}
public static event PropertyChangedEventHandler? PropertyChanged;
}
I believe that there's no 'right' answer to your question but I hope there's something here that might move things forward for you.

Moving code behind xaml.cs to ViewModel in xamarin

I have coded my behind code logic in xaml.cs file and now i want to move my code from code behind to ViewModel. How can this be done apart from code refactoring.
I am new to xamarin
Here is my Code behind
namespace _somename
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CareingtonFeeSchedule : ContentPage
{
private OneDentalFeeScheduleService oneDentalFeeScheduleService;
private ObservableCollection<ProviderSearchViewModel> _allGroups;
private ObservableCollection<ProviderSearchViewModel> _expandedGroups;
protected ObservableCollection<Grouping<string, FeeScheduleItem>> feeScheduleGroups;
protected ObservableCollection<FeeScheduleItem> feeScheduleItems;
private readonly AppViewModel AppViewModelInstance;
private Plugin.Geolocator.Abstractions.Position currentPosition;
private FeeScheduleModel feeScheduleDataResult;
public CareingtonFeeSchedule(AppViewModel appViewModel)
{
InitializeComponent();
AppViewModelInstance = appViewModel;
BindingContext = AppViewModelInstance;
AppViewModelInstance.IsActivityLoading = true;
LoadFeeeSchedule();
}
private void HeaderTapped(object sender, EventArgs args)
{
int selectedIndex = _expandedGroups.IndexOf(
((ProviderSearchViewModel)((Button)sender).CommandParameter));
_allGroups[selectedIndex].Expanded = !_allGroups[selectedIndex].Expanded;
UpdateListContent();
}
async Task OnHomeFeeScheduleTapped_TappedAsync(object sender, EventArgs args)
{
await Navigation.PushAsync(new AccLandPage(AppViewModelInstance));
}
private void ProviderBar_TextChanged(object sender, TextChangedEventArgs e)
{
var keyword = ProviderSearchBar.Text;
GroupedView.ItemsSource =
_expandedGroups.Where(s =>
s.Title.ToLower().Contains(keyword.ToLower()));
}
private void UpdateListContent()
{
_expandedGroups = new ObservableCollection<ProviderSearchViewModel>();
foreach (ProviderSearchViewModel group in _allGroups)
{
ProviderSearchViewModel newGroup = new ProviderSearchViewModel(group.Title, group.ShortName, group.Expanded);
if (group.Expanded)
{
foreach (Plan plan in group)
{
newGroup.Add(plan);
}
}
_expandedGroups.Add(newGroup);
}
GroupedView.ItemsSource = _expandedGroups;
}
public FeeScheduleModel FeeScheduleDataResult
{
protected set
{
feeScheduleDataResult = value;
OnPropertyChanged(nameof(FeeScheduleDataResult));
}
get => feeScheduleDataResult;
}
protected int feeScheduleCount;
public int FeeScheduleCount => feeScheduleCount;
private async Task<bool> LoadFeeeSchedule()
{
try
{
if (oneDentalFeeScheduleService == null)
{
oneDentalFeeScheduleService = new OneDentalFeeScheduleService("1dental.com");
}
var feeSchedRes = await oneDentalFeeScheduleService.GetFeeScheduleAsync(AppViewModelInstance.ZipCode, string.Empty, CancellationToken.None);
if (feeSchedRes?.Schedule?.Count > 0)
{
ConvertFeeScheuleDict(feeSchedRes.Schedule);
}
else FeeScheduleDataResult = null;
return true;
}
catch (Exception eX)
{
with the fee schedule lookup: \n{eX.Message}", "OK");
return false;
}
finally
{
AppViewModelInstance.IsActivityLoading = false;
actInd.IsRunning = false;
}
}
private void ConvertFeeScheuleDict(Dictionary<string, List<FeeScheduleItem>> feesche)
{
ObservableCollection<ProviderSearchViewModel> list = new ObservableCollection<ProviderSearchViewModel>();
ProviderSearchViewModel psm = null;
foreach (var item in feesche)
{
psm = new ProviderSearchViewModel(item.Key, "");
foreach (var valitem in item.Value)
{
Plan p = new Plan();
p.Code = valitem.Code;
p.CostDisplay = valitem.CostDisplay;
p.Description = valitem.ProcedureSecondary;
p.Name = valitem.Procedure;
psm.Add(p);
}
list.Add(psm);
}
_allGroups = list;
UpdateListContent();
}
private async void GetZipCode()
{
try
{
if (AppViewModelInstance.UserPosition == null)
{
try
{
var hasPermission = await Utils.CheckPermissions(Permission.Location);
if (!hasPermission)
{
await Navigation.PushAsync(new MainScreen());
return;
}
}
catch (Exception ex)
{
Debug.WriteLine($"Exception occurred while looking permission during Appearing event: {ex}");
}
var locator = CrossGeolocator.Current;
currentPosition = await locator.GetPositionAsync(new TimeSpan(0, 0, 0, 10, 0));
var addressList = await locator.GetAddressesForPositionAsync(currentPosition, null);
AppViewModelInstance.UserPosition = currentPosition;
foreach (var item in addressList)
{
AppViewModelInstance.ZipCode = item.PostalCode;
ZipCodeEntry.Text = item.PostalCode;
break;
}
}
else
{
var locator = CrossGeolocator.Current;
currentPosition = AppViewModelInstance.UserPosition;
var addressList = await locator.GetAddressesForPositionAsync(currentPosition, null);
foreach (var item in addressList)
{
AppViewModelInstance.ZipCode = item.PostalCode;
ZipCodeEntry.Text = item.PostalCode;
break;
}
}
LoadFeeeSchedule();
}
catch (Exception ex)
{
Debug.WriteLine($"Exception occurred while looking up location during Appearing event: {ex}");
}
}
private void ZipCodeEntry_Complete(object sender, EventArgs e)
{
if (sender != null)
{
AppViewModelInstance.ZipCode = ((Entry)sender).Text;
}
}
private void ZipCodeEntry_Changed(object sender, EventArgs e)
{
if (sender != null)
{
string _text = ((Entry)sender).Text; //Get Current Text
if (_text.Length > 5) //If it is more than your character restriction
{
_text = _text.Remove(_text.Length - 1); // Remove Last character
ZipCodeEntry.Text = _text; //Set the Old value
}
if (_text.Length == 5)
{
AppViewModelInstance.ZipCode = _text;
LoadFeeeSchedule();
}
}
}
public bool CanRefreshExecute(string tempVal = null)
{
if (AppViewModelInstance.IsRefreshing) return false;
var valToCheck = tempVal ?? AppViewModelInstance.ZipCode;
if (string.IsNullOrEmpty(valToCheck) || string.IsNullOrWhiteSpace(valToCheck)) return false;
bool isDigitString = true;
foreach (var c in valToCheck)
{
if (char.IsDigit(c)) continue;
isDigitString = false;
}
if (isDigitString) AppViewModelInstance.ZipCode = valToCheck;
return isDigitString;
}
private void GroupedView_ItemTapped(object sender, ItemTappedEventArgs e)
{
}
}
}
just export your code to the view model and set the view model as binding context of the Page. For example in the constructor:
//In the code behind
PageViewModel viewModel;
public Page()
{
this.BindingContext = viewModel = new PageViewModel();
//...
}
The ViewModel should implement INotifyPropertyChanged.
(Functions which are triggered by events have to stay in the code behind and access the view model through the ViewModel Property)

xamarin.ios mp3 streaming from url stops before its end

I'm developing an ios app with xamarin, that contains some mp3 files to stream in the app.
i used thissample, and it seems to work fine...
but the mp3 stops before its end, always at around 2' 30"
this is the code i used:
public partial class PlayerViewController : UIViewController
{
NSTimer updatingTimer;
StreamingPlayback player;
public event EventHandler<ErrorArg> ErrorOccurred;
public string SourceUrl { get; private set; }
public string Title { get; private set; }
public PlayerOption PlayerOption { get; private set; }
public bool IsPlaying { get; private set; }
public PlayerViewController (PlayerOption playerOption, string sourceUrl, string title) : base ("PlayerViewController", null)
{
PlayerOption = playerOption;
SourceUrl = sourceUrl;
Title = title;
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
this.View = View;
volumeSlider.TouchUpInside += SetVolume;
playPauseButton.TouchUpInside += PlayPauseButtonClickHandler;
headerMusic.Text = this.Title;
}
void SetVolume (object sender, EventArgs e)
{
if (player == null)
return;
player.Volume = volumeSlider.Value;
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
Title = PlayerOption == PlayerOption.Stream ? "Stream " : "Stream & Save";
playPauseButton.TitleLabel.Text = "Pause";
timeLabel.Text = string.Empty;
// Create a shared intance session & check
var session = AVAudioSession.SharedInstance ();
if (session == null) {
var alert = new UIAlertView ("Playback error", "Unable to playback stream", null, "Cancel");
alert.Show ();
alert.Clicked += (object sender, UIButtonEventArgs e) => alert.DismissWithClickedButtonIndex (0, true);
} else {
StartPlayback ();
IsPlaying = true;
// Set up the session for playback category
NSError error;
session.SetCategory (AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.DefaultToSpeaker);
session.OverrideOutputAudioPort (AVAudioSessionPortOverride.Speaker, out error);
}
}
public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear (animated);
if (updatingTimer != null)
updatingTimer.Invalidate ();
if (player != null) {
player.FlushAndClose ();
player = null;
}
}
void PlayPauseButtonClickHandler (object sender, EventArgs e)
{
if (player == null)
return;
if (IsPlaying)
player.Pause ();
else
player.Play ();
var title = IsPlaying ? "Play" : "Pause";
playPauseButton.SetTitle (title, UIControlState.Normal);
playPauseButton.SetTitle (title, UIControlState.Selected);
IsPlaying = !IsPlaying;
}
void StartPlayback ()
{
try {
var request = (HttpWebRequest)WebRequest.Create (SourceUrl);
request.BeginGetResponse (StreamDownloadedHandler, request);
} catch (Exception e) {
string.Format ("Error: {0}", e.ToString ());
}
}
void RaiseErrorOccurredEvent (string message)
{
var handler = ErrorOccurred;
if (handler != null)
handler (this, new ErrorArg { Description = message });
}
void StreamDownloadedHandler (IAsyncResult result)
{
var buffer = new byte [8192];
int l = 0;
int inputStreamLength;
double sampleRate = 0;
Stream inputStream;
AudioQueueTimeline timeline = null;
var request = result.AsyncState as HttpWebRequest;
try {
var response = request.EndGetResponse (result);
var responseStream = response.GetResponseStream ();
if (PlayerOption == PlayerOption.StreamAndSave)
inputStream = GetQueueStream (responseStream);
else
inputStream = responseStream;
using (player = new StreamingPlayback ()) {
player.OutputReady += delegate {
timeline = player.OutputQueue.CreateTimeline ();
sampleRate = player.OutputQueue.SampleRate;
};
InvokeOnMainThread (delegate {
if (updatingTimer != null)
updatingTimer.Invalidate ();
updatingTimer = NSTimer.CreateRepeatingScheduledTimer (0.5, (timer) => RepeatingAction (timeline, sampleRate));
});
while ((inputStreamLength = inputStream.Read (buffer, 0, buffer.Length)) != 0 && player != null) {
l += inputStreamLength;
player.ParseBytes (buffer, inputStreamLength, false, l == (int)response.ContentLength);
InvokeOnMainThread (delegate {
progressBar.Progress = l / (float)response.ContentLength;
});
}
}
} catch (Exception e) {
RaiseErrorOccurredEvent ("Error fetching response stream\n" + e);
Debug.WriteLine (e);
InvokeOnMainThread (delegate {
if (NavigationController != null)
NavigationController.PopToRootViewController (true);
});
}
}
void RepeatingAction (AudioQueueTimeline timeline, double sampleRate)
{
var queue = player.OutputQueue;
if (queue == null || timeline == null)
return;
bool disc = false;
var time = new AudioTimeStamp ();
queue.GetCurrentTime (timeline, ref time, ref disc);
playbackTime.Text = FormatTime (time.SampleTime / sampleRate);
}
string FormatTime (double time)
{
double minutes = time / 60;
double seconds = time % 60;
return String.Format ("{0}:{1:D2}", (int)minutes, (int)seconds);
}
Stream GetQueueStream (Stream responseStream)
{
var queueStream = new QueueStream (Environment.GetFolderPath (Environment.SpecialFolder.Personal) + "/copy.mp3");
var t = new Thread ((x) => {
var tbuf = new byte [8192];
int count;
while ((count = responseStream.Read (tbuf, 0, tbuf.Length)) != 0)
queueStream.Push (tbuf, 0, count);
});
t.Start ();
return queueStream;
}
}
how can i solve this problem?
thanks in advance
Had the same issue.
Open the StreamingPlayback.cs file
Change the variable
int bufferSize = 128 * 1024
Into
int bufferSize = 128 * 128
Or try other sizes..
It worked for me

Method that does not proceed until event gets fired

I have a GUI that is linked to several Sensors through COM-Port. I have a Datahandler that makes sure that the received and sent packages have the correct size and content. And then i have a SensorContainer containing all Sensors, firing an event whenever a new sensor is added!
My problem is now:
I need a method, that can initialize all sensors with one click.
And i just cant do it. If I initialize them one by one, it works, but all in one method doesnt work.
My idea is that the packages get mixed and the handler doesnt accept the sensors anymore for initialization. So i tried to make the method wait for the first sensor to be initialized and the proceed, but i have no clue how!
I tried a few things with mulitthreading, but im not really experienced in that field and it didnt work out in the end anyway...
SensorContainerClass:
public static class SensorContainer
{
public static event EventHandler<ContainerEvent> ContainerChanged;
public static ObservableCollection<Sensor> SensorList
{
get { return _sensorList; }
}
public static void AddSensor(Sensor sensor)
{
Sensor sensorExists = GetSensor(sensor.Address);
if (sensorExists == null)
{
Application.Current.Dispatcher.Invoke(() => _sensorList.Add(sensor));
ContainerChanged(null,new ContainerEvent(sensor.Address, true));
}
else
{
Console.WriteLine("It's not possible to add multiple sensors with the same address.");
}
}
public static void RemoveSensor(Sensor sensor)
{
Sensor sensorExists = GetSensor(sensor.Address);
if (sensorExists != null)
{
Application.Current.Dispatcher.Invoke(() => _sensorList.Remove(sensor));
ContainerChanged(null, new ContainerEvent(sensor.Address, false));
}
else
{
Console.WriteLine("No sensor with address " + sensor.Address);
}
}
public static Sensor GetSensor(byte address)
{
foreach (Sensor sensor in _sensorList)
{
if (sensor.Address == address)
{
return sensor;
}
}
return null;
}
// members
private static readonly ObservableCollection<Sensor> _sensorList = new ObservableCollection<Sensor>();
}
DataHandler:
public class DataHandler:ModelBase
{
private const int MAX_TIMER_TIME = 500;
public long answertime_milli;
Timer aTimer;
Stopwatch watch;
Sensor tempSensor;
private byte[] tempFrame;
private bool framePartPendingFlag = false;
public bool framesEqual;
public bool initalisationFlag = false;
public InitType initialisationType;
public byte[] TxFrame
{
get { return txFrame; }
}
public byte TxAddress
{
get { return TxFrame[0]; }
}
public byte TxLength
{
get { return TxFrame[1]; }
}
public byte TxCommand
{
get { return TxFrame[2]; }
}
public byte[] TxData
{
get { return TxFrame.GetRange(3, TxLength - 2); }
}
public byte TxChecksum
{
get { return TxFrame[TxLength - 1]; }
}
private byte[] txFrame;
private bool successfull;
public bool Successfull
{
get
{
return successfull;
}
set
{
if(successfull != value)
{
successfull = value;
this.OnPropertyChanged();
}
}
}
public DataHandler()
{
txFrame = new byte[4] { 0, 0, 0, 0 };
aTimer = new Timer(MAX_TIMER_TIME);
watch = new Stopwatch();
CommandManager.Instance.Init();
InterfaceWrapper.Instance.SerialPort.DataReceived += OnSerialPortReceived;
}
public void InitializeSensor(InitType type, byte address, Int32 serialNumber = 0)
{
if (SensorContainer.GetSensor(address) != null )
{
MessageBox.Show("Sensoraddress already used");
return;
}
foreach(Sensor temp in SensorContainer.SensorList)
{
if(temp.Serialnr == serialNumber)
{
MessageBox.Show("Sensor with the same SerialNumber already initalized");
return;
}
}
byte[] frame;
if (type == InitType.INIT_TO_ONE_DEVICE)
{
Sensor.COM_ADDR_SET_ONE_TX initStruct = new Sensor.COM_ADDR_SET_ONE_TX();
initStruct.Address = address;
frame = createFrame(initStruct, 0xFF, 0x20);
}
else
{
Sensor.COM_ADDR_SET_TO_SN_TX initStruct = new Sensor.COM_ADDR_SET_TO_SN_TX();
initStruct.Address = address;
if (serialNumber == 0)
{
MessageBox.Show("Serialnumber is missing");
return;
}
initStruct.SerialNumber = serialNumber;
frame = createFrame(initStruct, 0x00, 0x22);
}
setTxFrame(frame);
InterfaceWrapper.Instance.SerialPort.SendData(frame);
initalisationFlag = true;
initialisationType = type;
}
public void StartDataTransfer(Sensor sensor, byte commandid)
{
if (sensor == null)
{
MessageBox.Show("No such sensor");
return;
}
Command command = sensor.getCommand(commandid);
if (command == null)
{
MessageBox.Show("Command does not exist");
return;
}
foreach (KeyValuePair<CommandAttribute, Command> pair in sensor.CommandList)
{
if (pair.Value == command)
{
timeout = pair.Key.Timeout;
transmission = pair.Key.Transmission;
break;
}
}
tempSensor = sensor;
if (SensorContainer.GetSensor(sensor.Address) != null)
{
byte[] endFrame = createFrame(command, sensor.Address, commandid);
setTxFrame(endFrame);
if (true)
{
InterfaceWrapper.Instance.SerialPort.SendData(txFrame);
}
}
else
{
MessageBox.Show("Sensor not yet initialized");
}
}
private byte[] createFrame(Command command, byte address, byte commandId)
{
byte[] data = MarshalHelper.Serialize(command);
byte[] frame = new byte[4 + data.Length];
frame[0] = address;
frame[1] = (byte)frame.Length;
frame[2] = commandId;
Buffer.BlockCopy(data, 0, frame, 3, data.Length);
frame[frame.Length - 1] = GenerateChecksum(frame);
return frame;
}
public void OnSerialPortReceived(object sender, ComDataRxEvent e)
{
byte[] data = e.Data;
setRxFrame(data);
}
public void setTxFrame(byte[] _txFrame)
{
Successfull = false;
txFrame = _txFrame;
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = false;
aTimer.Start();
watch.Start();
}
public void setRxFrame(byte[] _rxFrame)
{
if (framePartPendingFlag)
{
byte[] newTempFrame = new byte[tempFrame.Length + _rxFrame.Length];
tempFrame.CopyTo(newTempFrame, 0);
_rxFrame.CopyTo(newTempFrame, tempFrame.Length);
framePartPendingFlag = false;
setRxFrame(newTempFrame);
}
else if (!checkMinLength(_rxFrame))
{
tempFrame = _rxFrame;
framePartPendingFlag = true;
}
else if (!checkBit(_rxFrame))
{
tempFrame = _rxFrame;
framePartPendingFlag = true;
}
else
{
framePartPendingFlag = false;
aTimer.Stop();
watch.Stop();
answertime_milli = watch.ElapsedMilliseconds;
if (!initalisationFlag)
{
if (checkAll(_rxFrame))
{
writeCommandToSensor(_rxFrame);
}
}
else
{
createSensorAfterInitialization(_rxFrame);
}
}
}
private void writeCommandToSensor(byte[] frame)
{
Command expCommand = CommandManager.Instance.GetRxCommand(tempSensor.GetType(), TxCommand);
Command command = MarshalHelper.Deserialize(expCommand, frame.GetRange(3, frame[1] - 2));
tempSensor.setCommand(command);
}
private void createSensorAfterInitialization(byte[] frame)
{
if (initalisationFlag && initialisationType == InitType.INIT_TO_ONE_DEVICE)
{
Sensor.COM_ADDR_SET_ONE_RX expCommand = new Sensor.COM_ADDR_SET_ONE_RX();
Sensor.COM_ADDR_SET_ONE_RX command = (Sensor.COM_ADDR_SET_ONE_RX)MarshalHelper.Deserialize(expCommand, frame.GetRange(3, frame[1] - 2));
Sensor tempSensor = Sensor.GetSensorInstance(command.SerialNumber, command.Address, command.SensorName, command.SensorSubName, command.dataStructType, command.dataStructSubType,command.pcbVersion,command.firmware);
tempSensor.setCommand(command);
SensorContainer.AddSensor(tempSensor);
initalisationFlag = false;
}
else
{
Sensor.COM_ADDR_SET_TO_SN_RX expCommand = new Sensor.COM_ADDR_SET_TO_SN_RX();
Sensor.COM_ADDR_SET_TO_SN_RX command = (Sensor.COM_ADDR_SET_TO_SN_RX)MarshalHelper.Deserialize(expCommand, frame.GetRange(3, frame[1] - 2));
if (command == null)
{
MessageBox.Show("Sensor not answering");
}
else {
Sensor tempSensor = Sensor.GetSensorInstance(command.SerialNumber, command.Address, command.SensorName, command.SensorSubName, command.dataStructType, command.dataStructSubType, command.pcbVersion, command.firmware);
tempSensor.setCommand(command);
SensorContainer.AddSensor(tempSensor);
initalisationFlag = false;
}
}
}
public void OnTimedEvent(object source, ElapsedEventArgs e)
{
framePartPendingFlag = false;
}
}

Background Worker Call Method or something

I have a method/procedure which works well, however it takes ages to do its thing so I want to move it into a background worker so people can still use the app.
Here is the code. (I cut down as much as I could)
public partial class NetworkInformation : UserControl, INotifyPropertyChanged
{
public NetworkInformation()
{
InitializeComponent();
Discovery();
}
public void Discovery()
{
GetIcon Icon = new GetIcon();
BitmapImage IconOfComputer = null;
List<DiscoveredComputer> NetworkedComputers = new List<DiscoveredComputer>();
DirectoryEntry Discover = new DirectoryEntry("WinNT://Workgroup");
BitmapImage On = Icon.LoadIcon(#"/Images/Icons/ComputerOn.ico");
BitmapImage Off = Icon.LoadIcon(#"/Images/Icons/ComputerOff.ico");
foreach (DirectoryEntry Node in Discover.Children)
{
try
{
if (Node.Properties.Count > 0)
{
IconOfComputer = On;
}
}
catch
{
IconOfComputer = Off;
}
if (Node.Name != "Schema") { NetworkedComputers.Add(new DiscoveredComputer { Image = IconOfComputer, ComputerName = Node.Name, MyToolTip = "Node Type = " + Node.SchemaEntry.Name }); }
}
ListView_LocalComputers.ItemsSource = NetworkedComputers;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class DiscoveredComputer : INotifyPropertyChanged
{
private string _ComputerName;
public string ComputerName
{
get { return _ComputerName; }
set
{
_ComputerName = value;
this.NotifyPropertyChanged("ComputerName");
}
}
private BitmapImage _Image;
public BitmapImage Image {
get { return _Image; }
set
{
_Image = value;
this.NotifyPropertyChanged("Image");
}
}
private String _MyToolTip;
public String MyToolTip
{
get { return _MyToolTip; }
set
{
_MyToolTip = value;
this.NotifyPropertyChanged("ToolTip");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class GetIcon
{
public BitmapImage IconStorage { get; set; }
public BitmapImage LoadIcon(String IconPath)
{
BitmapImage GeneratedIcon = new BitmapImage();
GeneratedIcon.BeginInit();
GeneratedIcon.UriSource = new Uri("pack://application:,,," + IconPath, UriKind.RelativeOrAbsolute);
GeneratedIcon.EndInit();
IconStorage = GeneratedIcon;
return GeneratedIcon;
}
}
}
This all works awesomely, somehow...
Here is the code I:developed for my background worker
public partial class MyBackgroundWorker : UserControl
{
WorkerData BGW;
public MyBackgroundWorker()
{
InitializeComponent();
BGW = new WorkerData();
#region Workers Events
BGW.ThisWorker.DoWork += new DoWorkEventHandler(Workers_DoWork);
BGW.ThisWorker.ProgressChanged += new ProgressChangedEventHandler(Workers_Progress);
BGW.ThisWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Workers_Completed);
BGW.ThisWorker.WorkerReportsProgress = true;
BGW.ThisWorker.WorkerSupportsCancellation = true;
#endregion
}
public void RibbonButton_EventClickStart(object sender, RoutedEventArgs e)
{
BGW.ThisWorker.RunWorkerAsync();
}
public void UserForm_Loaded(object sender, RoutedEventArgs e)
{
}
public void RibbonButton_EventClick(object sender, RoutedEventArgs e)
{
BGW.ThisWorker.CancelAsync();
}
public void Workers_DoWork(object sender, DoWorkEventArgs e)
{
}
public void Workers_Progress(object sender, ProgressChangedEventArgs e)
{
BGW.ThisWorkersProgress = e.ProgressPercentage;
}
public void Workers_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) { BGW.ThisWorkersResult = "Cancelled By User"; }
else if (e.Error != null) { BGW.ThisWorkersResult = "Error Encountered: " + e.Error.Message; }
else
{
BGW.ThisWorkersResult = "Task Completed Successfully";
BGW.WorkersReturnObject = e.Result;
}
}
}
public class WorkerData
{
public BackgroundWorker ThisWorker { get; set; }
public int ThisWorkersProgress { get; set; }
public string ThisWorkersResult { get; set; }
public object WorkersReturnObject { get; set; }
public object ThisWorkersJob { get; set; }
public WorkerData()
{
ThisWorker = new BackgroundWorker();
}
}
So how do I get my background worker to run the Discovery method I have created?
You need to do your work in the DoWork event handler.
I don't know if you need a whole separate class for this. I prefer to create these as I need them, on the fly. I think you'll get yourself shoehorned, where you'll use your class in multiple places and then decide you want to do something else in Workers_Completed in certain cases, or do something different when an error occurs in certain cases, and that one class could end up being a tangled-up pain. That's just my opinion though.
Also, you have to be very careful about touching the UI thread from your BackgroundWorker. In the example below, I'm passing in your node count to the DoWork event, instead of having it possibly touch a UI component directly. I'm also passing the list to the RunWorkerCompleted event, so that you're back in the main thread when it tries to attach the list to your ListView.
var bw = new BackgroundWorker();
bw.DoWork += (s, e) =>
{
var nodePropertiesCount = (int)e.Argument;
// the guts of `Discovery` go in here
e.Result = NetworkedComputers;
};
bw.RunWorkerCompleted += (s, e) =>
{
if (e.Error != null)
{
// Task Completed Successfully
ListView_LocalComputers = (List<DiscoveredComputer>)e.Result;
}
else
{
// Error Encountered
}
};
bw.RunWorkerAsync(Node.Properties.Count);
SLaks answer is correct, but you apparently don't understand what that means. I'd suggest taking the guts of Discover() and putting them in the Workers_DoWork() method like this:
public void Workers_DoWork(object sender, DoWorkEventArgs e)
{
var backgroundWorker = sender as BackgroundWorker;
GetIcon Icon = new GetIcon();
BitmapImage IconOfComputer = null;
List<DiscoveredComputer> NetworkedComputers = new List<DiscoveredComputer>();
DirectoryEntry Discover = new DirectoryEntry("WinNT://Workgroup");
BitmapImage On = Icon.LoadIcon(#"/Images/Icons/ComputerOn.ico");
BitmapImage Off = Icon.LoadIcon(#"/Images/Icons/ComputerOff.ico");
while (!backgroundWorker.CancellationPending)
{
foreach (DirectoryEntry Node in Discover.Children)
{
try
{
if (Node.Properties.Count > 0)
{
IconOfComputer = On;
}
}
catch
{
IconOfComputer = Off;
}
if (Node.Name != "Schema") { NetworkedComputers.Add(new DiscoveredComputer { Image = IconOfComputer, ComputerName = Node.Name, MyToolTip = "Node Type = " + Node.SchemaEntry.Name }); }
}
break;
}
if(backgroundWorker.CancellationPending)
{
e.Cancel = true;
}
else
{
e.Result = NetworkedComputers;
}
}
And then modifying your Workers_Completed() like this:
public void Workers_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) { BGW.ThisWorkersResult = "Cancelled By User"; }
else if (e.Error != null) { BGW.ThisWorkersResult = "Error Encountered: " + e.Error.Message; }
else
{
BGW.ThisWorkersResult = "Task Completed Successfully";
//BGW.WorkersReturnObject = e.Result;
//background worker can't touch UI components
ListView_LocalComputers.ItemsSource = e.Result as List<DiscoveredComputer>;
}
}
I suggest these changes, or something similar, because the background worker can't modify/access UI components (like your ListView), so it has to pass back the value to use for the ListView view its Result property. I also included a simple way of detecting cancellation; I'll leave progress reporting up to you to implement.

Categories

Resources