I'm working on a football simulator and i have 9 matches in backgroung on separate threads. And in the method what's in the heart of each thread, there is an event. And when that even occurs (when a goal is "kicked"), I want to update a label (named goalLabel) on the form with the partial result. I wrote a code...:
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0) homeGoals++;
else awawyGoals++;
if (goal != null) goal(this); //(goal is an event)
Thread.Sleep(1000);
} //this is the full method
...where on each match the exact count of the goals will be 6(and the result will be 3 - 3), so with 9 (9 is fix too) background matches, the goalLabel should change text (6*9=)54 times. However it only changes a few times.
And here's the event's eventhandler method:
public void GoalEventHandler(Match match)
{
string akt = string.Format("{0} {1} - {2} {3}", match.Opps[0].Name, match.Hgoals, match.Agoals, match.Opps[1].Name);
UpdateGoalLabel(akt);
}
And the UpdateGoalLabel method:
public void UpdateGoalLabel(string update)
{
if (InvokeRequired)
{
MyDel del = new MyDel(UpdateGoalLabel); // yeah, I have a delegate for it: delegate void MyDel(string update);
Invoke(del, update);
}
else
{
lock (this) // if this lock isn't here, it works the same way
{
this.goalLabel.Text = update;
}
}
}
So I can reach and change the label's text, but I dont why it don't changes 54 times. And that would be the goal, to get notified after every single goal.
Any idea?
Thank you in advance.
Update #1:
I'm using VS2010.
Here's the code where I launch the threads:
List<Thread> allMatches = new List<Thread>();
foreach (Match match in matches)
{
Thread newtmatch = new Thread(match.PlayMatch); //this is the first code block I wrote
allMatches.Add(newtmatch);
newtmatch.Start();
}
Update #2:
Here's where I attach the eventhandlers (this is in the same method, few lines above the previous code block):
matches = new List<Match>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
Match vmi = new Match(opponents);
matches.Add(vmi);
vmi.goal += new Match.goalevent(GoalEventHandler);
}
//Program.cm.nextMatches is a List<Team[]> object that contains the pairs of teams for the next matches;
And I convert those Team arrays to a Match object, because this class has two Team fields, and has the event and the PlayMatch method which is still the method that contains (only) the first code block.
Let me make sure I am understanding what you are doing. You have started 9 threads that loop 6 times, each time updating a text box and sleeping for 1 second. From the sounds of it they are all updating the same label. You commented that if you push the updates to a list you get them all, my guess is that all 9 threads are updating so fast you can't see it and the last one wins.
I am not sure how you are building your UI but I think this would be perfect for WPF and MVVM(Model-View-ViewModel). I wouldn't use WinForms unless you had a damn good reason, WPF is so much easier to work with.
I would create a few view models:
public class MainWindowViewModel : DispatcherObject, INotifyPropertyChanged
{
public MainWindowViewModel()
{
Matches = new ObservableCollection<MatchViewModel>();
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MatchViewModel> _matches;
public ObservableCollection<MatchViewModel> Matches
{
get { return _matches; }
set
{
_matches = value;
OnPropertyChanged("Matches");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MatchViewModel : DispatcherObject, INotifyPropertyChanged
{
public MatchViewModel()
{
HomeTeam = new TeamViewModel();
AwayTeam = new TeamViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private TeamViewModel _homeTeam;
public TeamViewModel HomeTeam
{
get { return _homeTeam; }
set
{
_homeTeam = value;
OnPropertyChanged("HomeTeam");
}
}
private TeamViewModel _awayTeam;
public TeamViewModel AwayTeam
{
get { return _awayTeam; }
set
{
_awayTeam = value;
OnPropertyChanged("AwayTeam");
}
}
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
OnGoalScored(HomeTeam);
else
OnGoalScored(AwayTeam);
Thread.Sleep(1000);
}
}
private void OnGoalScored(TeamViewModel team)
{
if (!team.Dispatcher.CheckAccess())
{
team.Dispatcher.Invoke((Action<TeamViewModel>)OnGoalScored, team);
}
else
{
team.Score++; // do other stuff here
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TeamViewModel : DispatcherObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private int _score;
public int Score
{
get { return _score; }
set
{
_score = value;
OnPropertyChanged("Score");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in the program class on the UI thread, do something like this:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindowViewModel mainWindow = new MainWindowViewModel();
List<Thread> matchThreads = new List<Thread>();
foreach (Team[] opponents in Program.cm.nextMatches)
{
MatchViewModel match = new MatchViewModel();
match.HomeTeam.Name = opponents[0].Name;
match.AwayTeam.Name = opponents[1].Name;
mainWindow.Matches.Add(match);
Thread matchThread = new Thread(match.PlayMatch);
matchThreads.Add(matchThread);
matchThread.Start();
}
MainWindow = new MainWindow();
MainWindow.DataContext = mainWindow;
MainWindow.Show();
}
I did mine in the override for OnStartup because in VS2010 when you create a project you would have your starup item inherit from System.Windows.Application.
I have this simple UI for testing:
<Window x:Class="TestMatch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ItemsControl ItemsSource="{Binding Matches}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1} - {2} {3}">
<Binding Path="HomeTeam.Name"/>
<Binding Path="HomeTeam.Score"/>
<Binding Path="AwayTeam.Name"/>
<Binding Path="AwayTeam.Score"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This code is pretty crude and thrown together to give a quick demo of a MVVM way of doing this. I was able to get all 9 teams to update every second. I had some filler code to simulate the objects you had:
public partial class Program
{
protected override void OnStartup(StartupEventArgs e)
{
...
}
private class Team
{
public string Name { get; set; }
}
private static class cm
{
static cm()
{
nextMatches =
new Team[][]
{
new[] { new Team { Name = "TeamA" }, new Team { Name = "TeamB" }},
new[] { new Team { Name = "TeamC" }, new Team { Name = "TeamD" }},
new[] { new Team { Name = "TeamE" }, new Team { Name = "TeamF" }},
new[] { new Team { Name = "TeamG" }, new Team { Name = "TeamH" }},
new[] { new Team { Name = "TeamI" }, new Team { Name = "TeamJ" }},
new[] { new Team { Name = "TeamK" }, new Team { Name = "TeamL" }},
new[] { new Team { Name = "TeamM" }, new Team { Name = "TeamN" }},
new[] { new Team { Name = "TeamO" }, new Team { Name = "TeamP" }},
new[] { new Team { Name = "TeamQ" }, new Team { Name = "TeamR" }},
};
}
public static Team[][] nextMatches { get; set; }
}
}
I've had problems with UI refreshes too and have worked with "BackgroundWorker" threading directly instead of just "Thread" class. The BackgroundWorker thread allows you to report progress changed explicitly and call some method outside the thread (ie: the calling UI thread that invoked the secondary thread). So, I've created a custom class derived from the background worker, and also created my own version of the "Match" class you have described
public class Match
{
public Match( string Home, string Away )
{
HomeTeam = Home;
HomeGoals = 0;
AwayTeam = Away;
AwayGoals = 0;
}
// simple properties with PROTECTED setters, yet readable by anyone
public string HomeTeam
{ get; protected set; }
public int HomeGoals
{ get; protected set; }
public string AwayTeam
{ get; protected set; }
public int AwayGoals
{ get; protected set; }
// just place-holder since I don't know rest of your declarations
public EventHandler goal;
public void PlayMatch()
{
for (int i = 0; i < 6; i++)
{
if (i % 2 == 0)
HomeGoals++;
else
AwayGoals++;
// Report to anyone listening that a score was made...
if (goal != null)
goal(this, null);
Thread.Sleep(1000);
}
}
}
// Now, the background worker
public class MatchBGW : BackgroundWorker
{
// each background worker preserves the "Match" instance it is responsible for.
// this so "ReportProgress" can make IT available for getting values.
public Match callingMatch
{ get; protected set; }
// require parameter of the match responsible for handling activity
public MatchBGW(Match m)
{
// preserve the match started by the background worker activity
callingMatch = m;
// tell background worker what method to call
// using lambda expression to cover required delegate parameters
// and just call your function ignoring them.
DoWork += (sender, e) => m.PlayMatch();
// identify we can report progress
WorkerReportsProgress = true;
// Attach to the match. When a goal is scored, notify ME (background worker)
m.goal += GoalScored;
}
// this is called from the Match.
public void GoalScored(object sender, EventArgs e)
{
// Now, tell this background worker to notify whoever called IT
// that something changed. Can be any percent, just something to
// trigger whoever called this background worker, so reported percent is 1
ReportProgress(1);
}
}
Now, from your calling window that has the label, such as started from a button click...
private void button1_Click(object sender, RoutedEventArgs e)
{
// create your "Matches" between teams
matches = new List<Match>();
matches.Add(new Match("A", "B"));
matches.Add(new Match("C", "D"));
matches.Add(new Match("E", "F"));
foreach (Match m in matches)
{
// create an instance of background worker and pass in your "match"
MatchBGW bgw = new MatchBGW(m);
// tell the background worker that if it is notified to "
// report progress" to, to pass itself (background worker object)
// to this class's SomeoneScored method (same UI thread as textbox)
bgw.ProgressChanged += SomeoneScored;
// Now, start the background worker and start the next match
bgw.RunWorkerAsync();
}
}
// This is called from the background worker via "ReportProgress"
public void SomeoneScored(object sender, ProgressChangedEventArgs e)
{
// Just ensuring that the background worker IS that of what was customized
if (sender is MatchBGW)
{
// get whatever "match" associated with the background worker
Match m = ((MatchBGW)sender).callingMatch;
// add it's latest score with appropriate home/away team names
this.txtAllGoals.Text +=
string.Format("{0} {1} - {2} {3}\r\n",
m.HomeTeam, m.HomeGoals, m.AwayGoals, m.AwayTeam );
}
}
Yes, it may be more code, but I am explicitly handling who is called back and reporting what on their proper thread... no BeginInvoke required test / actions. Just an alternative to your issue.
Related
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.
I use 2 Timers, 1 Check all my Citect tags for changed values each second. The other one is a KeepAlive Timer for a TCP Connection.
This is how i call the Timers:
Timer timer = new Timer(ctApiCtrl.CheckUpdateAllInputTags, "updatetags", 1000, 1000);
Timer timer2 = new Timer(mepCtrl.KeepAlive, "test", 3000, 3000);
Now due the nature of how the existing systems works together is with writing and reading PLC/DSK tags. So if my service wants to detect these changes it needs to periodically "poll" or check these values.
This is the class where those tags comes from:
public class TagWithValue : INotifyPropertyChanged
{
public string TagName { get; set; }
private string tagValue;
public TagCategory TagCategory { get; set; }
public string TagValue
{
get { return tagValue; }
set
{
if(tagValue != value)
{
tagValue = value;
RaisePropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
So when the property TagValue changes it detects it and fire x.
public void CheckUpdateAllInputTags(object objectInfo)
{
VDGSenseController.Authenticate();
if (listOfTags.Video == null || listOfTags.Audio == null)
{
Logger.Info("Empty Tag list , reinitliazing all tags");
InitiliazeAllTags();
}
foreach (var tag in Tags)
{
try
{
var value = TagRead(tag.TagName); //read Tag value
tag.TagValue = value;
tag.PropertyChanged += d_PropertyChanged;
}
catch (Exception ex)
{
Logger.Warn(ex,"Importing tag unsuccesfull");
continue;
}
}
Tags = Tags.Where(t => !string.IsNullOrWhiteSpace(t.TagValue)).Distinct().ToList();
}
Here lies the problem of this snippet.
private void d_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
//trigger wanneer prop value is changed
var tagchanged = (CtModel.TagWithValue)sender;
Logger.Info($"Tag: {tagchanged.TagName} with value: {tagchanged.TagValue} has changed.");
}
The problem i have with this code is that it fires multiple times the propertychanged method in the same timer tick.
Note the logging that contains Tag: DK_OM_2A
How can i change this so it only fires once each timer tick?
I fixed it by moving the logic from the propertychanged in a different method and in the new code it goes like this:
private void d_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
//trigger wanneer prop value is changed
var tagchanged = (CtModel.TagWithValue)sender;
tagsWithChangedValue.Add(tagchanged);
tagsWithChangedValue = tagsWithChangedValue.Distinct().ToList();
}
#region UpdateTagsPropertyChanged
public void UpdateTagsPropertyChanged(List<CtModel.TagWithValue> listofchangedtags)
{
if(listofchangedtags.Count == 0)
{
return; // if list is empty, go back
}
foreach(var tagchanged in listofchangedtags)
{
logger.Info($"Tag: {tagchanged.TagName} with value: {tagchanged.TagValue} has changed.");
switch (tagchanged.TagName)
{
default:
break;
}
}
tagsWithChangedValue.Clear(); //clears list for the next cycle
}
#endregion
I developed an application on Windows 10 Universal App who use MVVM but I have a big problem with it.
I would add an ObservableCollection item(created on a second window) to the MVVM and then, show the new item on the ListView of MainPage but it doesn't refresh!
The 2 windows are always open
http://i.stack.imgur.com/WSo6v.jpg
The code of MVVMList.cs
public class MVVMList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<THEFile> onglets_cache = new ObservableCollection<THEFile>();
public ObservableCollection<THEFile> onglets_list
{
get
{
return onglets_cache;
}
set
{
onglets_cache = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
public MVVMList()
{
onglets_list = new ObservableCollection<THEFile>();
Fonctions fonctions = new Fonctions();
fonctions.LoadOnglets(onglets_cache);
}
}
The code of the second page(always open) - CreateFile.xaml.cs
private void create_butt_Click(object sender, RoutedEventArgs e)
{
Fonctions fonc = new Fonctions(); MVVMList main = new MVVMList();
fonc.SetupNew(main.onglets_list, "test" + ".php", "");
}
//SetupNew on Fonctions.cs
public async void SetupNew(ObservableCollection<THEFile> list, string name, string content)
{
FolderPicker folderpick = new FolderPicker();
folderpick.ViewMode = PickerViewMode.List;
folderpick.FileTypeFilter.Add(".html"); folderpick.FileTypeFilter.Add(".htm"); folderpick.FileTypeFilter.Add(".HTML");
folderpick.FileTypeFilter.Add(".php"); folderpick.FileTypeFilter.Add(".PHP");
folderpick.FileTypeFilter.Add(".css"); folderpick.FileTypeFilter.Add(".CSS");
folderpick.FileTypeFilter.Add(".js"); folderpick.FileTypeFilter.Add(".JS");
StorageFolder storage_file = await folderpick.PickSingleFolderAsync();
if (storage_file != null)
{
MainPage vm = new MainPage();
list.Add(new THEFile { NameOfFile = name, PathOfFile = storage_file.Path + "\\" + name, CodeOfFile = content, already_opened = false, line = 0 });
string path = storage_file.Path + #"\" + name;
StorageFile file_create = await storage_file.CreateFileAsync(name, CreationCollisionOption.GenerateUniqueName);
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.Add(file_create);
SaveOnglets(list);
}
}
And on the MainPage.xaml (always open)
...
<ListView x:Name="onglets" x:FieldModifier="public" ItemTemplate="{StaticResource Templa}" ItemsSource="{Binding onglets_list}" SelectionChanged="onglets_SelectionChanged" Margin="0,117,0,57" Visibility="Visible" ContainerContentChanging="onglets_ContainerContentChanging">
...
Thank you!
In your XAML, try using a Collection View Source.
Add this to the top of your xaml:
<Page.Resources>
<CollectionViewSource x:Name="MakesCollectionViewSource" IsSourceGrouped="True"/>
</Page.Resources>
Set your ListView:
ItemsSource="{Binding Source={StaticResource MakesCollectionViewSource}}"
Then in your code when you have a List of items assign it using
MakesCollectionViewSource.Source = /* Some List<GroupInfoList<object>> that is generated from onglets_list*/
I create my List like this but it may not be relevant because this is to make all of my object names alphabetical:
internal List<GroupInfoList<object>> GetGroupsByLetter()
{
var groups = new List<GroupInfoList<object>>();
var query = from item in MakeList
orderby ((Make)item).MakeName
group item by ((Make)item).MakeName[0] into g
select new { GroupName = g.Key, Items = g };
foreach (var g in query)
{
var info = new GroupInfoList<object>();
info.Key = g.GroupName;
foreach (var item in g.Items)
{
info.Add(item);
}
groups.Add(info);
}
return groups;
}
public class GroupInfoList<T> : List<object>
{
public object Key { get; set; }
public new IEnumerator<object> GetEnumerator()
{
return (System.Collections.Generic.IEnumerator<object>)base.GetEnumerator();
}
}
where MakeList is my observable collection and Make are the objects in the collection and MakeName is a string I am trying to alphabetize
And then call using
MakesCollectionViewSource.Source = GetGroupsByLetter();
If I understand your code and requirements correctly, I think part of the problem is that you "new up" your MVVMList and your MainPage everytime you click the create button.
So, without getting into suggestions about using MVVM Light and an IOC container, you could quickly accomplish what you're trying to do by making your MVVMList class a singleton and having your MainPage use it for a data context. When your other window adds to the MVVMList.onglets collection, it will be immediately reflected in your currently open MainPage. Let me know if you need some code snippets. Good luck!
[Edit below]
I had a few minutes left on lunch, so here is an over-simplified example. Again, without getting into what MVVM is and is not. Personally, I would do this differently, but that would be outside the scope of your question. Full disclosure - this is in WPF, but same logic applies, I just don't have Windows 10 on the PC that I'm using. I also simplified the collection to be of type string. This is not intended to copy/paste into your code as it will not work in your example - but should easily transfer.
MVVMList class:
public class MVVMList: INotifyPropertyChanged
{
//Singleton section
private static MVVMList instance;
private MVVMList() { }
public static MVVMList Instance
{
get
{
if (instance == null)
{
instance = new MVVMList();
}
return instance;
}
}
//end singleton section
private ObservableCollection<string> _onglets = new ObservableCollection<string>();
public ObservableCollection<string> Onglets
{
get { return _onglets; }
set
{
if (_onglets != value)
{
_onglets = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
}
//INotify implementation
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage:
<ListView x:Name="onglets" x:FieldModifier="public" ItemsSource="{Binding Onglets}" />
MainPage.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = MVVMList.Instance;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var x = new CreateWindow();
x.Show();
}
}
CreateWindow.cs:
private void CreateButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(StringTextBox.Text))
{
MVVMList.Instance.Onglets.Add(StringTextBox.Text);
}
}
I have a class, say Person, with an Id and a name. This class properly implements INotifyPropertyChanged
Addition: some people asked for class Person.
My real problem is a more elaborate class, I've simplified it to a fairly simple POCO to be certain it was not because of my class.
Originally:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
}
For updates it needed to implement INofityChanged. The full code is at the end of this question
StackOverflow: How to properly implement INotifyPropertyChanged
I have a System.Windows.Forms.Form
This form has a BindingSource.
The DataSource property of the binding source is set to my class Person
I have a DataGridView that is bound to the BindingSource
I have added several Person instances to the binding source
The added persons are properly shown.
If I programmatically change a Person in the bindingsource, the changed value is properly displayed.
So far so good. Problems arise if the Person is changed in a separate thread.
I regularly get the an InvalidOperationException with the message
BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
I guess this has something to do with the fact that the update is done in a an awaitable async Task. I know that before updating a user interface item you should check if InvokeRequired and act accordingly.
private void OnGuiItemChanged()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
}
else
{
... // update Gui Item
}
}
However, when using a binding source the changes are handled inside the bindingsource. So I can't check for InvokeRequired
So how to update items that are also stored in a binding source in a non-UI thread?
By request: implementation of class Person and some code of my form
class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int id = 0;
private string name = null;
public int Id
{
get { return this.id; }
set { this.SetField(ref this.id, value); }
}
public string Name
{
get { return this.name; }
set { this.SetField(ref this.name, value); }
}
protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
RaiseEventPropertyChanged(propertyName);
}
}
private void RaiseEventPropertyChanged(string propertyName)
{
var tmpEvent = this.PropertyChanged;
if (tmpEvent != null)
{
tmpEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some code of the form:
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 10; ++i)
{
var person = new Person()
{
Id = i,
Name = "William " + i.ToString(),
};
this.bindingSource1.Add(person);
}
}
private void buttonStart_Click(object sender, EventArgs e)
{
this.cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
Task.Run(() => ChangePersonsAsync(this.cancellationTokenSource.Token));
}
private async Task ChangePersonsAsync(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
foreach (var p in this.bindingSource1)
{
Person person = (Person)p;
person.Id = -person.Id;
}
await Task.Delay(TimeSpan.FromSeconds(0.01), token);
}
}
catch (TaskCanceledException)
{
}
}
As you mentioned, the changes are handled inside the BindingSource class, so the easiest way I see is to replace it with the following
public class SyncBindingSource : BindingSource
{
private SynchronizationContext syncContext;
public SyncBindingSource()
{
syncContext = SynchronizationContext.Current;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (syncContext != null)
syncContext.Send(_ => base.OnListChanged(e), null);
else
base.OnListChanged(e);
}
}
Just make sure it's created on the UI thread.
I dont know if it possible but what I want is something like that
In WinForm listbox1 has a list of lines(copied from file)
In another Thread and class I run on a List that contains the same lines each line I parse and DoSomething
once I finish with that line I want the index in the listbox to change
from my basic and limited understanding my approach was with an Event to fire in form and than maybe using Invoke (for not to cross thread )
Is there is a way to somehow bind to index of the listbox somehow with my for/foreach loop ?
class form
{
listBoxScript.SetSelected(ScriptCounter, true);<--bind the ScriptCounter?
}
in another Class
class RunScript
{
//..
public void RunScriptList()
{
ScriptCounter = 0 ;
foreach ( var cell in ScriptList)
{
ScriptCounter ++;
//DoSomething
}
}
}
Make sure you implement INotifyPropertyChanged in RunScript class. Here's a complete sample:
class RunScript : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int scriptCounter;
private ISynchronizeInvoke invoker;
public RunScript(ISynchronizeInvoke invoker)
{
if (invoker == null) throw new ArgumentNullException("invoker");
this.invoker = invoker;
}
public async void RunScriptList()
{
ScriptCounter = 0;
foreach (var cell in Enumerable.Range(1, 15))
{
ScriptCounter++;
await Task.Delay(1000);
//DoSomething
}
}
public int ScriptCounter
{
get { return scriptCounter; }
set
{
scriptCounter = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
Action action = () => handler(this, new PropertyChangedEventArgs(propertyName));
invoker.Invoke(action, null);
}
}
}
private RunScript rs;
public Form1()
{
InitializeComponent();
rs = new RunScript(this)
var binding = new Binding("SelectedIndex", rs, "ScriptCounter", false, DataSourceUpdateMode.OnPropertyChanged);
listBox1.DataBindings.Add(binding);
}
private void button1_Click(object sender, EventArgs e)
{
rs.RunScriptList();
}
Note I have used async/await in RunScriptList, If you do it in another thread you need to fire PropertyChanged event in main thread to avoid cross thread exception.