Change value of dynamically created progressbar - c#

I created dynamically a bunch of textboxes, labels, and progressbar in a TableLayoutPanel
These I want to control from a Parallel.Foreach threads.
I know the name of each control so I can access them like so:
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<name>].Text = "text to write";
}));
But when I try to change the value of a progressbar or I try to change the style(blocks->marquee) it doesn't know the .Value of .Style option.
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<progressbar_name>].Value = <percentage>;
}));
Table1.Invoke(
new MethodInvoker(delegate() {
Table1.Controls[<progressbar_name>].Style = ProgressBarStyle.Marquee;
}));
It gives an error that .Value and .Style cannot exist in the current context.

You might want cast Table1.Controls[<progressbar_name>] to a ProgressBar class.

You need to cast control to use ProgressBar properties.
Try this:
Table1.Invoke(new MethodInvoker(delegate()
{
((ProgressBar)Table1.Controls[<progressbar_name>]).Value = <percentage>;
((ProgressBar)Table1.Controls[<progressbar_name>]).Style = ProgressBarStyle.Marquee;
}));
Happy to help you!

When you refer Table1.Controls[<progressbar_name>], this is interpreted as a Control, instead of as a ProgressBar, which happens to be a subclass of Control.
A simple fix:
var progBar = Table1.Controls[<progressbar_name>] as ProgressBar;
if(progBar != null)
{
progBar.Value = = <percentage>;
}
The if-structure should not be necessary, strictly speaking, but might in some cases avoid exceptions by skipping the attempt to set the value instead (e.g. in case the controls in your Table were not loaded or instantiated correctly).

Related

Adding winform progressbars to a list

I have a windows form application that has eight separate progress bars for eight independent tasks (progressBar0 - progressBar7). I want to add these to a list in my code so I can iterate through them.
I declare the list:
List<Control> progressBar = new List<Control>();
Now I iterate through my controls and add those that contain "progressBar" in their name:
foreach (Control control in Controls)
{
if (control.Name.Contains("progressBar")) { progressBar.Add(control); }
}
So far so good. But, I can't set the .Value property:
progressBar[0].Value == 100; <-- this doesn't work
The above won't compile and gives error "Control does not contain a definition for 'Value'". Strangely, if I use intellisense I can drill down into progressBar and find the Value parameter. But for some reason I can't access it programatically and I can't retrieve it from the watch list, either.
I can access the value through AccessibilityObject but is in string format and will be annoying to work with:
progressBar[0].AccessibilityObject.Value = "100%" <-- this works fine
If I access each progressBar directly, everything is fine:
progressBar0.Value = 100; <-- this works fine
What am I doing wrong?
You are using a list of Control, which is the base type for all the control. Value is a property that is specific only to some of the control, in this case, the ProgressBar.
You need to cast it to a ProgressBar:
var progressBars = new List<ProgressBar>();
foreach (var control in Controls)
{
// this ensure the type is a ProgressBar, unlike name, it cant be randomly assigned
if (control is ProgressBar)
{
// a cast is needed here
progressBars.Add(control as ProgressBar);
}
}
progressBars[0].Value = 100;

Retrieve TextBox By Name

In my project i know the names of TextBoxes which are dynamically generated is there any solution to retrieve this TextBox text from other methods.In other sense i want to get TextBox by name and want to use in other part of code.
I have TextBox allocated like this...
private void Met(string rowNo)
{
TextBox t2 = new TextBox();
t2.Name = "itemAmt" + rowNo;
PurchaseItemEntryDyPanel.Controls.Add(t2);
}
Is there any way other than using name? Any Solution?
I personaly use name when I want to read posted data from a form.
And I would use Id when controls are supposed to be unique. So the code is a little different:
var t2 = new TextBox();
t2.ID = "itemAmt" + rowNo;
//since you mention in the comments, add it to the panel
yourPanel.Controls.Add(t2);
Then to get the textBox value
var controlId = "itemAmt" + rowNo;
var t2 = ((TextBox)(yourPanel.FindControl(controlId)));
if(t2 != null)
{
//do someting
//t2.Text = "something";
//t2.Enabled = true;
}
If you are not willing to make that change, go over the solution posted earlier.
You can get your TextBox from Controls collection of Form by it's name like this:
var myTextBox = this.Controls[textBoxName];
You don't show too much of your code, but I assume you're adding it to the collection of controls on your form. Otherwise, the TextBox you create in Met goes out of scope when your method ends, just like any other local variable.
private void Met(string rowNo)
{
TextBox t2 = new TextBox();
t2.Name = "itemAmt" + rowNo;
this.Controls.Add(t2); // need to add the TextBox to your form's controls
}
Then you can use Selman22's solution or, if the control might be added to a GroupBox or Panel, you'll want to search all child controls too:
var myControl = this.Controls.Find("itemAmt4", true);
if (myControl != null)
myControl.Enabled = true;
Use this in your Class:
foreach (Control tempCtrl in this.Controls)
{
// Determine he control is textBox1,
if (tempCtrl.Name == "itemAmt" + rowNo)
{
this.Controls.Remove(tempCtrl);
}
}

Wpf application and Threads

I have problem with my GUI and Threads.
The GUI contains DataGrid. Every X time the program do some query and getting a list of items that I want to fill into the DataGrid.
So far so good:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
}));
FindSelectionObject(); // <-- see next explanation.
}
When the user click on one of the objects in the datagrid, the line color changed (it works fine), but when the program reload the table,The painted line disappears (Becuse I clear and add new objects).
To deal with it, I created the function FindSelectionObject():
private void FindSelectionObject()
{
this.Dispatcher.Invoke((Action)(() =>
{
this.SelectedIndex = TaskListTable.Items.IndexOf((myObject)lastSelectionObject); //find index of the new object that equels to the last selection object.
var row = TaskListTable.ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as DataGridRow; //get the row with the index
row.Background = Brushes.LightGoldenrodYellow; //repaint
}));
}
The problem: Everything works fine, but sometimes when the program reloads, the line flashes per second and then highlighted back, and sometimes it's not painting it at all (untill the next reload).
I can't understand why this is happening. I think maybe the FindSelectionObject() begins to run before the loadTaskList() ends to invoke all and add the new objects into the datagrid.
But if so - Why? And how can I fix it?
In the bottom line, I want that after every reload the line re-paint immediately..
Thanks for any advice!
A few things to think about:
You should keep in mind that the DataGrid uses virtualization, which means that each item in your items source does not get its very own UI element. The UI elements are created to fill the visible area, and then re-used depending on which data-source item is currently bound to each one (this changes when you scroll for instance or change the items source). This may cause you problems in the future if you use your current approach, so keep this in mind.
The other thing is that the DataGrid may require more "cycles" of the layout process in order to update its UI. You may simply be calling FindSelectionObject prematurely. You have queued FindSelectionObject right after the invocation in loadTaskList. If the DataGrid needs to perform some actions which are queued on the dispatcher after the items source has changed, these will execute after the invocation in FindSelectionObject.
Try this instead:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
// The items of the grid have changed, NOW we QUEUE the FindSelectionObject
// operation on the dispatcher.
FindSelectionObject(); // <-- (( MOVE IT HERE )) !!
}));
}
EDIT: OK, so if this fails then maybe this will cover the case in which the above solution fails: subscribe to the LoadingRow event of DataGrid and set the appropriate background color if the row is the selected one. So in the cases when new rows are created this event will be called (due to virtualization it is not called per item in items source, but per actual row UI element). In the event args you will have access to the created DataGridRow instance.
I think this issue could be a visual thread synchronization. For this you can create and use a method similar like this:
public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
{
var currentSyncContext = SynchronizationContext.Current;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (_, __) =>
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
currentSyncContext.Send((t) =>
{
IsBusy = true;
BusyText = string.IsNullOrEmpty(text) ? "Espere por favor..." : text;
if (beforeVisualAction != null)
beforeVisualAction();
}, null);
action();
currentSyncContext.Send((t) =>
{
IsBusy = false;
BusyText = "";
if (afterVisualAction != null)
afterVisualAction();
}, null);
};
backgroundWorker.RunWorkerAsync();
}
IsBusy and BusyText are particular properties, that you can remove. The action variable will be the action to do in background (load your items for instance). beforeVisualAction and afterVisualAction are the visual actions you may want to do before and after the background action. Here are any visual update, for instance select your item, change color, set a view model variable that raise a binding update,... (any action that update the view).
Hope this method helps.
Are you maintaining the reference to lastSelectionObject somewhere? You say you're adding new objects, if they are truly new then the reference will be different and the reference comparison happening in IndexOf will not find it.

WPF Dispatcher issue when adding tab items to tab control

I'm getting an error when running this code:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
the tabcontrol1 is hard coded into the xaml and the tab item/s are built at runtime.
I'm getting an error:
TargetInvocationException was
unhandled Exception has been thrown by
the target of an invocation.
I'd love to hear any thoughts on this.
Thanks
UPDATE
the inner exception:
{"The calling thread cannot access
this object because a different thread
owns it."}
the full method code:
TabItem tbItem = new TabItem();
tbItem.Header = worker;
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { tabControl1.Items.Add(tbItem); }));
The method is called with this:
Thread newThread = new Thread(myMethod);
newThread.SetApartmentState(ApartmentState.STA);
newThread.Start();
ANOTHER UPDATE
This works:
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = datasource.Where(i => i.Category == worker);
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));
as you can see your tbItem is created on different thread, even through it tries to dispatch it back to the TAbControl's main gui thread.
why not extract out the part that takes longer (which you are usign thread for) and once you got result back, continue with creating tbItem and adding it to TabControl in the GUI thread
Example:
tabControl.Dispatcher.Invoke calls below function with dataSoruce result it gets
List<string> result = null;
tabControl.Dispatcher.Invoke((Action<IEnumerable<string>>)ProcessResult, result);
public void ProcessResult(IEnumerable<string> datasource)
{
//this is where you do creating TabItem and assigning data source to it and adding to TabControl.
}
Haven't compiled, pls check syntax
Check the InnerException property to find out the reason. TargetInvocationException is just a wrapper by the wpf runtime. Your lambda probably throws, but without the actual exception you can't tell.
edit
You are creating your TabItem in a different thread, thus the GUI thread can't access it, even though you use the dispatcher for the actual adding. As you already posted with your last snippet, you have to create the TabItem in the GUI thread. Only do the calculation in a different thread, and once the result returns, do the actual creation of the TabItem in the GUI thread (if necessary via the Dispatcher).
The problem is that you're creating your UIElements on a separate thread. This is not allowed.
You can do your processing on a background thread (the call to datasource.Where(i => i.Category == worker);), but unfortunately, every UI element needs to be constructed and used entirely on the main user interface thread.
In your case, this means constructing your ListBox and Grid on the UI thread, inside the Dispatcher call.
I would suggest rewriting this as:
// Load the data on the background...
var data = datasource.Where(i => i.Category == worker);
// Do all work on the UI thread
tabControl1.Dispatcher.Invoke(DispatcherPriority.Normal,
(Action)(() =>
{
TabItem tbItem = new TabItem();
tbItem.Header = worker;
//Grid & ListBox(within tab item)
Grid grid = new Grid();
ListBox listBox = new ListBox();
listBox.HorizontalAlignment = HorizontalAlignment.Stretch;
listBox.VerticalAlignment = VerticalAlignment.Stretch;
listBox.ItemsSource = data;
grid.Children.Add(listBox);
tbItem.Content = grid;
tabControl1.Items.Add(tbItem);
}));

Access to a Label on the Form by my Custom control

I have created a WindowsFormControlLibrary porject. It works fine, I can drop it on the forms,call its methods,etc ...
but now as a property of it, I am passing the name of a Label to it. and I want this custom control to be able to use that label name and for example change its font to bold .
so the question is that if I have a WinForm and I have a Label on that form and my custom control on that form, then how can I tell my custom control to do something with that label which I am passing its name to it?
Instead of sending in the name of the label, send in a reference to the actual label and then the custom control can both read the name if it needs to and change the label's font and other properties.
Be careful though, it can quickly get messy to keep track of what's happening if various forms and controls change controls on other forms etc.
Edit: Added code to do what you ask for in the comments
Code isn't tested so it might not be completely correct, but something similar to this should work.
foreach (Control c in Parent.Controls)
{
if (c is Label)
{
Label l = (Label)c;
// do stuff to label l
}       
}
First, if you wish to access a Control from your UserControl, you will need to use the FindForm() method.
Second, you will be required to expose your TextBox control, for example, through a property of your form.
Then, you would need to know the type of this Form returned by this FindForm() method.
Once you know it, you need to type-cast this result to the correct type.
So, here a sample untested pseudo-code to give you the idea:
public partial class MyMainForm {
private TextBox textBox1;
public MyMainForm() {
textBox1 = new Textbox();
textBox1.Name = #"textBox1";
textBox1.Location = new Point(10, 10);
textBox1.Size = new Size(150, 23);
this.Controls.Add(textBox1);
}
public Font MyTextBoxFont {
get {
return textBox1.Font;
} set {
if (value == null) return;
textbox1.Font = value;
}
}
}
Then, assuming you have dropped your control on your form, your UserControl could have a property like so:
public partial class MyUserControl {
private Form GetContainerForm {
get {
return this.FindForm();
}
}
// And later on, where you need to set your TextBox's font:
private void SetContainerInputFieldFont(Font f) {
if (GetContainerForm == null) return; // Or throw, depending on what you need to do.
((MyMainForm)GetContainerForm).MyTextBoxFont = f
}
}
cool :) I just added a get set public property of type Label... it automatically lists all the label on the form.

Categories

Resources