I recently encountered a situation for which I have no explanation so far
Trying to work on cross-threading operations with winform I wrote a short piece of code to test solutions faster.
I have a form including a progress bar, a a DataGridView and a Button
public MainForm()
{
this._progressBar = new ProgressBar { Dock = DockStyle.Top, };
this._button = new Button { Dock = DockStyle.Bottom, Text = #"&GO!" };
this._dataGridView = new DataGridView {Dock = DockStyle.Fill,};
this.Controls.Add(this.ProgressBar);
this.Controls.Add(this.Button);
this.Controls.Add(this.DataGridView);
this.WindowsFormsSynchronizationContext = WindowsFormsSynchronizationContext.Current as WindowsFormsSynchronizationContext;
this._records = new SpecialBindingList<Record>();
//this._records = new SpecialBindingList<Record>();
this.DataGridView.DataSource = this.Records;
this.Button.Click += this.button_Click;
}
The button has an event OnClick
private void button_Click(object sender, EventArgs e)
{
var dispatcherUI = Dispatcher.CurrentDispatcher;
Action action = () =>
{
while (true)
{
Task.Factory.StartNew(() =>
{
var value = (this.ProgressBar.Value == this.ProgressBar.Maximum)
? 0
: this.ProgressBar.Value + 1;
var record = new Record
{
A = DateTime.Now.Second.ToString(),
B = DateTime.Now.Millisecond.ToString()
};
if (Thread.CurrentThread != dispatcherUI.Thread)
{
dispatcherUI.BeginInvoke(new Action(() => { ProgressBar.Value = value; }), null);
}
this.WindowsFormsSynchronizationContext.Send((state) => this.Records.Add(record), null);
Thread.Sleep(100);
});
}
};
var task = new Task(action);
task.Start();
this.Button.Enabled = false;
}
It creates a task, which is filling the progress bar and adding new lines to the grid, then start it and disable the button.
The problem is, is I start the code like this the action (state) => this.Records.Add(record) would always throw an InvalidOperationException when adding a record to my Bindinglist.
However I realized that if I skip the this.Button.Enabled = false; line my code would execute without any problem !
This seems a little odd to me, so I was wondering why modifying a button property would creates an exception on an apparently unrelated operation
Related
I'm making a retro-style game with C# .NET-Framework, and for dialogue I'm using a for-statement, that prints my text letter by letter (like a typewriter-effect):
I'm working with different scenes, and I have a skip button (bottom right) that skips the current dialogue and passes to the next scene. My typewriter-effect automatically stops when all the text is displayed, but when I click on the skip button, it automatically skips to the next scene.
I would like it, when the typewriter is still active, and if I click on the skip button, that it first shows all the text, instead of skipping to the next scene.
So that it only skips to the next scene when all the text is displayed (automatically or manually).
This is the (working code) that I'm using for my typewriter method (+ variables):
public string FullTextBottom;
public string CurrentTextBottom = "";
public bool IsActive;
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage != null) // only runs on backgrounds that arent black
{
for(i=0; i < FullTextBottom.Length + 1; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i); // updating current string with one extra letter
LblTextBottom.Text = CurrentTextBottom; // "temporarily place string in text box"
await Task.Delay(30); // wait for next update
#region checks for IsActive // for debugging only!
if(i < FullTextBottom.Length + 1)
{
IsActive = true;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
if(CurrentTextBottom.Length == FullTextBottom.Length)
{
IsActive = false;
Debug1.Text = "IsActive = " + IsActive.ToString();
}
#endregion
}
}
}
And this is the code that I want to get for my skip button (named Pb_FastForward):
private void PbFastForward_Click(object sender, EventArgs e)
{
if( //typewriter is active)
{
//print all text into the textbox
}
else if( //all text is printed)
{
// skip to the next scene
}
}
But I don't know how to formulate the 2nd part of code. I've tried many different approaches, like using counters that increase on a buttonclick (and using that to check in an if-statement), and many different types of if-statements to see if the typewriter is still active or not, but I haven't got anything to work yet.
Edit
This is the sequence in which different components need to be loaded (on button click), which is related to the way different variables are updated:
Gamestate_Cycle() --> called for loading new scene.
FullTextBottom = LblTextBottom.Text --> called to refresh variables for typewriter.
TypeWriterEffectBottom() --> called to perform typewriter effect.
Avoid async void. Otherwise you can get an Exception that will break your game and you will not able to catch it.
Then use as less global variables in async methods as possible.
I suggest CancellationTokenSource as thread-safe way to stop the Type Writer.
public async Task TypeWriterEffectBottom(string text, CancellationToken token)
{
if (this.BackgroundImage != null)
{
Debug1.Text = "TypeWriter is active";
StringBuilder sb = new StringBuilder(text.Length);
try
{
foreach (char c in text)
{
LblTextBottom.Text = sb.Append(c).ToString();
await Task.Delay(30, token);
}
}
catch (OperationCanceledException)
{
LblTextBottom.Text = text;
}
Debug1.Text = "TypeWriter is finished";
}
}
Define CTS. It's thread-safe, so it's ok to have it in global scope.
private CancellationTokenSource cts = null;
Call TypeWriter from async method to be able to await it.
// set button layout as "Skip text" here
using (cts = new CancellationTokenSource())
{
await TypeWriterEffectBottom(yourString, cts.Token);
}
cts = null;
// set button layout as "Go to the next scene" here
And finally
private void PbFastForward_Click(object sender, EventArgs e)
{
if (cts != null)
{
cts?.Cancel();
}
else
{
// go to the next scene
}
}
I pondered on your task a bit more and it occurred to me that it is a good job for the Rx.Net library.
An advantage of this approach is that you have less mutable state to care about and you almost don't need to think about threads, synchronization, etc.; you manipulate higher-level building blocks instead: observables, subscriptions.
I extended the task a bit to better illustrate Rx capabilities:
there are two pieces of animated text, each one can be fast-forwarded separately;
the user can fast-forward to the final state;
the user can reset the animation state.
Here is the form code (C# 8, System.Reactive.Linq v4.4.1):
private enum DialogState
{
NpcSpeaking,
PlayerSpeaking,
EverythingShown
}
private enum EventKind
{
AnimationFinished,
Skip,
SkipToEnd
}
DialogState _state;
private readonly Subject<DialogState> _stateChanges = new Subject<DialogState>();
Dictionary<DialogState, (string, Label)> _lines;
IDisposable _eventsSubscription;
IDisposable _animationSubscription;
public Form1()
{
InitializeComponent();
_lines = new Dictionary<DialogState, (string, Label)>
{
{ DialogState.NpcSpeaking, ("NPC speaking...", lblNpc) },
{ DialogState.PlayerSpeaking, ("Player speaking...", lblCharacter) },
};
// tick = 1,2...
IObservable<long> tick = Observable
.Interval(TimeSpan.FromSeconds(0.15))
.ObserveOn(this)
.StartWith(-1)
.Select(x => x + 2);
IObservable<EventPattern<object>> fastForwardClicks = Observable.FromEventPattern(
h => btnFastForward.Click += h,
h => btnFastForward.Click -= h);
IObservable<EventPattern<object>> skipToEndClicks = Observable.FromEventPattern(
h => btnSkipToEnd.Click += h,
h => btnSkipToEnd.Click -= h);
// On each state change animationFarames starts from scratch: 1,2...
IObservable<long> animationFarames = _stateChanges
.Select(
s => Observable.If(() => _lines.ContainsKey(s), tick.TakeUntil(_stateChanges)))
.Switch();
var animationFinished = new Subject<int>();
_animationSubscription = animationFarames.Subscribe(frame =>
{
(string line, Label lbl) = _lines[_state];
if (frame > line.Length)
{
animationFinished.OnNext(default);
return;
}
lbl.Text = line.Substring(0, (int)frame);
});
IObservable<EventKind> events = Observable.Merge(
skipToEndClicks.Select(_ => EventKind.SkipToEnd),
fastForwardClicks.Select(_ => EventKind.Skip),
animationFinished.Select(_ => EventKind.AnimationFinished));
_eventsSubscription = events.Subscribe(e =>
{
DialogState prev = _state;
_state = prev switch
{
DialogState.NpcSpeaking => WhenSpeaking(e, DialogState.PlayerSpeaking),
DialogState.PlayerSpeaking => WhenSpeaking(e, DialogState.EverythingShown),
DialogState.EverythingShown => WhenEverythingShown(e)
};
_stateChanges.OnNext(_state);
});
Reset();
}
private DialogState WhenEverythingShown(EventKind _)
{
Close();
return _state;
}
private DialogState WhenSpeaking(EventKind e, DialogState next)
{
switch (e)
{
case EventKind.AnimationFinished:
case EventKind.Skip:
{
(string l, Label lbl) = _lines[_state];
lbl.Text = l;
return next;
}
case EventKind.SkipToEnd:
{
ShowFinalState();
return DialogState.EverythingShown;
}
default:
throw new NotSupportedException($"Unknown event '{e}'.");
}
}
private void ShowFinalState()
{
foreach ((string l, Label lbl) in _lines.Values)
{
lbl.Text = l;
}
}
private void Reset()
{
foreach ((_, Label lbl) in _lines.Values)
{
lbl.Text = "";
}
_state = DialogState.NpcSpeaking;
_stateChanges.OnNext(_state);
}
protected override void OnClosed(EventArgs e)
{
_eventsSubscription?.Dispose();
_animationSubscription?.Dispose();
base.OnClosed(e);
}
private void btnReset_Click(object sender, EventArgs e)
{
Reset();
}
I adjusted your code a little bit to achieve your goal. I'm not sure it's the best way to do it, but it should work.
public async void TypeWriterEffectBottom()
{
if(this.BackgroundImage == null)
{
return;
}
IsActive = true;
for(i=0; i < FullTextBottom.Length && IsActive; i++)
{
CurrentTextBottom = FullTextBottom.Substring(0, i+1);
LblTextBottom.Text = CurrentTextBottom;
await Task.Delay(30);
Debug1.Text = "IsActive = " + IsActive.ToString();
}
IsActive = false;
}
private void PbFastForward_Click(object sender, EventArgs e)
{
if(IsActive)
{
LblTextBottom.Text = FullTextBottom;
IsActive = false;
return;
}
// IsActive == false means all text is printed
// skip to the next scene
}
UPD: Just noticed that Hans Kesting has suggested pretty much exactly this in his comment.
You write what skip / forward button does, so you control it. Just have a check if the length of written text is equal to text that supposed to be written and if yes move as usual if not just display the text in full have delay to be read and move on
This is a C# web form project I am starting with after a long time away from IDE coding...
I am trying to make a simple custom dialog box class. This is my code.
public static class Dialogo
{
public static int show ()
{
Form dialogo = new Form();
dialogo.Width = 300;
dialogo.Height = 300;
Button btnSim = new Button() { Text = "Sim", Left = 30, Width = 100 };
Button btnNao = new Button() { Text = "Não", Left = 150, Width = 100 };
dialogo.Controls.Add(btnSim);
dialogo.Controls.Add(btnNao);
dialogo.ShowDialog();
// the following two lines are the problematic ones
btnSim += new EventHandler(btnSim_Click);
btnNao += new EventHandler(btnNao_Click);
return -1;
}
}
It's underlining the text within parenthesis and the message says:
The name btnSim_Click' does not exist in the current context
The problem is that I tried to add the following in my code but it doesn't let me put it anywhere (it always says that is something wrong):
private int btnNao_Click (object sender, EventArgs e)
{
return 0;
}
private int btnSim_Click (object sender, EventArgs e)
{
return 1;
}
My objective is that each of the both buttons btnSim and btnNao return a different value (say 1 and 0).
What am I doing wrong?
EventHandler is a delegate for a method that returns void.
Your methods return int.
Try something like this:
public static int show()
{
int returnValue = -1;
using (Form dialogo = new Form())
{
dialogo.Width = 300;
dialogo.Height = 300;
Button btnSim = new Button() { Text = "Sim", Left = 30, Width = 100 };
Button btnNao = new Button() { Text = "Não", Left = 150, Width = 100 };
dialogo.Controls.Add(btnSim);
dialogo.Controls.Add(btnNao);
btnSim.Click += (s, e) => { returnValue = 0; dialogo.DialogResult = DialogResult.OK; };
btnNao.Click += (s, e) => { returnValue = 1; dialogo.DialogResult = DialogResult.OK; };
dialogo.Disposed += (s, e) =>
{
btnSim?.Dispose();
btnSim = null;
btnNao?.Dispose();
btnNao = null;
};
dialogo.ShowDialog();
}
return returnValue;
}
I've run into a bit of a wall and I don't know how I've managed to stuff it up. I'm trying to have multiple panels on my application in C# and each slides in and out from the menu along the side. I've written a separate slide class:
class Slide
{
Panel pane;
Button btn;
bool hidden;
Timer t;
const int maxWidth = 315;
public Slide(Panel p, Button b)
{
this.pane = p;
this.btn = b;
hidden = true;
btn.Click += new EventHandler(btnClick);
t = new Timer();
t.Interval = 15;
t.Tick += new EventHandler(timeTick);
}
private void timeTick(object sender, EventArgs e)
{
if(hidden)
{
SlidingPane(+10);
}
else
{
SlidingPane(-10);
}
}
private void btnClick(object sender, EventArgs e)
{
t.Start();
}
private void SlidingPane(int i)
{
pane.Width += i;
if(pane.Width >= maxWidth || pane.Width <= 0)
{
t.Stop();
hidden = !hidden;
}
}
}
And I've initialised the panels as follows:
Slide menuP, calendarP, peopleP, taskP, settingsP;
public Form1()
{
InitializeComponent();
ButtonColours();
InitialisePanes();
}
private void InitialisePanes()
{
menuP = new Slide(menuPane, menuButton);
calendarP = new Slide(calendarPane, calendarButton);
peopleP = new Slide(peoplePane, peopleButton);
taskP = new Slide(taskPane, toDoButton);
settingsP = new Slide(settingsPane, settingsButton);
}
And here's the Form designer code for the working panel:
this.menuPane.BackColor = System.Drawing.Color.SlateGray;
this.menuPane.Controls.Add(this.peoplePane);
this.menuPane.Dock = System.Windows.Forms.DockStyle.Left;
this.menuPane.Location = new System.Drawing.Point(67, 0);
this.menuPane.Name = "menuPane";
this.menuPane.Size = new System.Drawing.Size(0, 652);
this.menuPane.TabIndex = 2;
And the others are exactly the same. Eg:
this.peoplePane.BackColor = System.Drawing.Color.SlateGray;
this.peoplePane.Controls.Add(this.calendarPane);
this.peoplePane.Dock = System.Windows.Forms.DockStyle.Left;
this.peoplePane.Location = new System.Drawing.Point(67, 0);
this.peoplePane.Name = "peoplePane";
this.peoplePane.Size = new System.Drawing.Size(0, 652);
this.peoplePane.TabIndex = 2;
I've started up my application and I click on the menuButton, it works. Slides in and out beautifully. I click on the others and....nothing happens.
Can anyone see why this is happening? Everything I'm looking at tells me that it should be working.
To make sure all panes are correctly aligned with each other and (not) nested you could use code like this:
foreach( Control ctl in new[] { peoplePane, calendarPane, taskPane, settingsPane })
{
ctl.Parent = menuPane.Parent;
ctl.Location = menuPane.Location;
}
It assumes that menuPane is at the right spot and makes all others sit right on top without nesting them.
The code you posted contained incorrect nesting and moving panels to the same spot with the mouse will also create (in this case unwanted) nesting. Moving with the keyboard avoids it but is tedious.
I have on winform with a textbox and on textchanged executes a background thread:
private void txtFathersLast_TextChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(_ => WaitWhileUserTyping());
}
private void WaitWhileUserTyping()
{
var keepWaiting = true;
while (keepWaiting)
{
_keyPressed = false;
Thread.Sleep(TypingDelay);
keepWaiting = _keyPressed;
}
Invoke((MethodInvoker)(ExecuteSearch));
_waiting = false;
}
private void ExecuteSearch()
{
Thread.Sleep(200);
Task.Factory.StartNew(() =>
{
using (DataReference.SearchWCF search = new DataReference.SearchWCF())
{
_similaritySearchResults = search.SearchPersonBySimilarity(txtFathersLast.Text, txtMothersLast.Text, txtName.Text, DateTime.Now, 10);
}
}).ContinueWith(t=>{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}));
}
else
{
if (_similaritySearchResults != null && _similaritySearchResults.Tables["data"].Rows.Count > 0)
{
DataTable dt = _similaritySearchResults.Tables["data"];
Infragistics.Win.Misc.UltraTile newTile = null;
for (int index = 0; index < dt.Rows.Count; index++)
{
newTile = new Infragistics.Win.Misc.UltraTile("Person X");
newTile.Control = new CustomControls.Controls.PersonResult("123", "123", index + 150);
newTile.Tag = new Guid("90D27721-7315-4B86-9CFD-4F7D02921E9A");
newTile.DoubleClick += TileDoubleClick;
tilePanel.Tiles.Add(newTile);
}
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
This is working fine, the application goes to a database then get results and update the UI, adding tiles to a control depending of the number of records returned by database.
Now, the problem comes when I try to add another background thread into my custom control:
new CustomControls.Controls.PersonResult("123", "123", index + 150);
The code for the control is:
protected override void InitLayout()
{
// if I comment this then everything works fine
// but if I leave this, then the UI freezes!!
GetPictureAsync();
base.InitLayout();
}
/// <summary>
///
/// </summary>
private void GetPictureAsync()
{
// This line needs to happen on the UI thread...
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
Random sleep = new Random();
System.Threading.Thread.Sleep(sleep.Next(1000,3000));
if (this.pbPhoto.InvokeRequired)
{
this.pbPhoto.Invoke(new Action(() =>
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}));
}
else
{
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
//this.pbPhoto.Image = Utility.Common.GetResourceImage("woman_sample.jpg");
}
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
So the problem seems to be that I first execute a thread for looking when to start search, then inside that thread I run another thread in order to get data from database, and then each control updated in the UI will run another thread to get a picture and update a picturebox.
Anyone knows how to solve this? or a way to work around this?
When you call
new CustomControls.Controls.PersonResult("123", "123", index + 150)
Is "123" a literal string, or are they being read from UI controls. For example,
new CustomControls.Controls.PersonResult(txtFathersName.Text", txtMothersName.Text, index + 150)
i cant test it right now, but isnt accessing the Text property not allowed from a thread other than the one that created the control?
I think the problem lies in you forcing the Task in GetPictureAsync to execute on UI thread and then you are calling Thread.Sleep(). This update UI in Task using TaskScheduler.FromCurrentSynchronizationContext question tackles the same problem as you are having. I would rewrite your code as:
private void async GetPictureAsync()
{
Random sleep = new Random();
await TaskEx.Delay(sleep.Next(1000,3000));
this.Load(#"E:\Photos\" + PhotoId.ToString() + ".jpg");
}
I am building a WPF application that converts a powerpoint to WPF elements when you select one from a list.
I am using MVVM light to bind a ViewModel to my view and to add communication between ViewModels.
I have two views: OpenLocalView and PresentationView. When I select a powerpoint in the OpenLocalView, a message will be sent by MVVM light to the ViewModel of PresentationView and the MainViewModel with the path to that powerpoint. The MainViewModel switches the view to the PresentationView, and the PresentationViewModel executes this code to convert the powerpoint, and when that is finished, set the current slide so it is shown in the PresentationView:
public void StartPresentation(string location)
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Loading = true;
Task.Factory.StartNew(() =>
{
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
scheduler).ContinueWith(x =>
{
Loading = false;
CurrentSlide = _slides.First();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
scheduler);
}
When the Loading property is set, the view gets updated with a "loading" message, to make the UI more responsive:
public Boolean Loading
{
get { return _loading; }
set
{
_loading = value;
RaisePropertyChanged("Loading");
}
}
The problem is, this executes properly the first time when I load a powerpoint: The view switches to the PresentationView, the "loading" message is displayed, and after the converting is finished, the message disappears and the slide is shown. But when I go back to the OpenLocalView, and choose another powerpoint, the OpenLocalView hangs and it switches to the PresentationView after the converter is finished, not showing the "loading" message at all.
For reference, I will add some more relevant code.
This is executed when a powerpoint is selected in the OpenLocalViewModel:
private void PerformOpenPresentation(string location)
{
Messenger.Default.Send<OpenPowerPointMessage>(new OpenPowerPointMessage {Location = location});
}
The MainViewModel is subscribed to the messenger and switches the view:
Messenger.Default.Register<OpenPowerPointMessage>(this,
delegate
{
if (_presentation == null) _presentation = new PresentationView();
CurrentView = _presentation;
});
The PresentationViewModel is subscribed to the messenger as well and executes the method shown above:
Messenger.Default.Register<OpenPowerPointMessage>(this, message => StartPresentation(message.Location));
So, what am I doing wrong? Again, it executes fine one time, then after that not anymore, although the same code is executed.
Maybe the UI isn't updated yet when you already start converting. Try waiting a few milliseconds between setting the Loading to true and the start of the converter thread :)
Look here:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Loading = true;
Task.Factory.StartNew(() =>
{
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
here ----> scheduler).ContinueWith(x =>
{
Loading = false;
CurrentSlide = _slides.First();
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
scheduler);
You're starting the 'long running' task on the synchronization context, that is - on the UI thread.
Get rid of the scheduler in the long running task, leave it on continuation. :)
I suggest moving the "Loading = true" in to task start block.. but make sure to use dispatcher while setting "Loading" value. i may not give the actual reason for the problem but its worth a try...
something like this may help..
Task.Factory.StartNew(() =>
{
System.Windows.Application.Current.Dispatcher.BeginInvoke((Action)delegate()
{
Loading = true;
});
var converterFactory = new ConverterFactory();
var converter = converterFactory.CreatePowerPointConverter();
_slides = converter.Convert(location).Slides;
}
Ok, I did it with windows forms, but I think is the same
I create a Label in the Task thread and call the Invoke of the form
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void AddControl(Control control)
{
if (InvokeRequired)
{
this.Invoke(new Action<Control>(AddControl), new object[] { control });
return;
}
this.Controls.Add(control);
}
private void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
var label = new Label
{
Location = new Point(0, 0),
Text = "hola",
ForeColor = Color.Black
};
this.Invoke(new Action<Control>(AddControl), new object[] { label });
});
}
}
EDIT
Ok how about using Dispather.Invoke I'm not sure if this is going to block the UI....
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void AddControl()
{
var l = new Label
{
Content = "Label",
Height = 28,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
Margin = new Thickness(209, 118, 0, 0),
Name = "label1",
VerticalAlignment = System.Windows.VerticalAlignment.Top
};
Grid.Children.Add(l);
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
Dispatcher.Invoke(new Action(AddControl), null);
});
}
}