Programmatically added LinkLabel not visible - c#

I am encountering a strange phenomenon: I have a WinForms application with four GroupBoxes, all four initially empty. I use this to track new followers/unfollowers on Twitter, planning on expanding its use once this functions properly.
It does work properly for new followers. For these I have a GroupBox called grpFollow, to which I add LinkLabels with the ScreenNames of my new followers like this:
var folTop = new Point(grpFollow.Left + 5, grpFollow.Top + 5);
lblFollowers.Text = Properties.Settings.Default.FollowersNow.Count.ToString();
lblFriends.Text = Properties.Settings.Default.FriendsNow.Count.ToString();
var ctr = 1;
foreach (var fol in newFollowers)
{
var kvp = LookupUser(fol);
if (string.IsNullOrEmpty(kvp.Key)) continue;
var linklabel = new LinkLabel()
{
Text = kvp.Value,
Width = 200,
Height = 15,
Location = folTop,
Visible = true,
Name = $"follbl{ctr}"
};
ctr++;
linklabel.Links.Add(0, linklabel.Width-1, $"https://twitter.com/{kvp.Key}");
linklabel.Click += Linklabel_Click;
grpFollow.Controls.Add(linklabel);
folTop.Y += 25;
}
LookupUser is just a function that passes the user id to the Twitter API and returns the name & screen_name of that user. Works fine, no problem. LinkLabels added nicely, no problem there either.
The trouble is with the other group boxes, e.g. the one for new friends:
folTop = new Point(grpFriends.Left + 15, grpFriends.Top + 15);
ctr = 1;
foreach (var fol in newFriends)
{
var kvp = LookupUser(fol);
if (string.IsNullOrEmpty(kvp.Key)) continue;
var llabel = new LinkLabel()
{
Text = kvp.Value,
Width = 200,
Height = 15,
Location = folTop,
Visible = true,
Name = $"frdlbl{ctr}"
};
ctr++;
llabel.Links.Add(0, llabel.Width - 1, $"https://twitter.com/{kvp.Key}");
llabel.Click += Linklabel_Click;
grpFriends.Controls.Add(llabel);
folTop.Y += 25;
}
As you can see, the logic is identical (because I want to extract this part to a separate method to avoid repetition). The location is set relative to the grpFriends group box, everything else is the same. Yet, the LinkLabel does not show, i.e. the second group box remains (visually) empty!
I have set a breakpoint to check what might go wrong. I single stepped through: the correct screen name is being retrieved, the location is correct, the control is added - but nothing ever shows up.
P.S: This code is in the RunWorkerCompleted method of a background worker, no further code is executed after this point.
Any idea why the Label isn't displayed?
Edit: I'll be damned!
I just changed the location of the grpFriends LinkLabel to 10,10: it appears, juuust clipping the lower border of my friends' group.
Now here is where this gets weird for me:
As you can see, the group has a Y value of 351. Point (10,10) should not even be in the box. So, it seems that the location is the culprit and the original code created the label outside the form.
Replacing grpFriends.Top as Y value with grpFriends.Top - grpFriends.Height got me closer. The LinkLabel is farther down from the top than I'd like but that's not so bad.
Very strange.

Okay, thanks to #raBinn I figured it out. The mistake here was me assuming, the LinkLabels needed a location relative to the Form. However, when adding them to the Controls collection of a GroupBox, they automatically assume a location relative to the GroupBox, not the Form.
So, my code is now basically the same for all four group boxes and looks like this:
PopulateGroup(newFollowers, grpFollow);
PopulateGroup(unFollow, grpLost);
PopulateGroup(newFriends, grpFriends);
PopulateGroup(unFriend, grpDitched);
And:
private void PopulateGroup(List<string> collPeople, GroupBox groupBox)
{
var folTop = new Point(12, 25);
foreach (var fol in collPeople)
{
var kvp = LookupUser(fol);
if (string.IsNullOrEmpty(kvp.Key)) continue;
var linklabel = new LinkLabel()
{
Text = kvp.Value,
Width = 200,
Height = 15,
Location = folTop
};
ctr++;
linklabel.Links.Add(0, linklabel.Width - 1, $"https://twitter.com/{kvp.Key}");
linklabel.Click += Linklabel_Click;
groupBox.Controls.Add(linklabel);
folTop.Y += 25;
}
}
So it doesn't really matter whether the group box is on the left or right, top or bottom of the form...

Related

add controls (Picturebox) in loop

What i have: Big PictureBox (lets call it Pic_Map) on the form. A class Ore.cs, A List<Ore> ores; and a database that pulls the data and places it into the ores list.
Functionality: So, The functionality of this is i have a TextBox/Combobox and a Button. When i press the Button, it will loop through the ores list and Dynamically add PictureBoxes ontop of the Pic_Map based on TexBox/ComboBox being equal to the data (in this case Ore_Name).
Problem: This all works fine, but the problem is that when i add the PictureBoxes Dynamically, it only seems to add the last value on the ores list (Red circles on Pic_Map). so, it ends up showing only 1 PictureBox instead of lets say 3, since i have 3 value Names that match with the TextBox/ComboBox.
Question: How to get it to work like when i write/choose "Flame Stone" it looks on all the data that has "Flame Stone" in its name and add it (Instead of it adding only the last value from the list).
Code:
private void PopulateComboBoxByName()
{
PictureBox ore_Area = new PictureBox();
db.GetOre(); //Getting data and putting it into "ores" list
foreach (Ore ore in db.ores)
{
if (CBOX_Filter.SelectedItem.ToString() == ore.Ore_Name)
{
int area_Width = Convert.ToInt32(ore.Area_Width);
int area_Height = Convert.ToInt32(ore.Area_Height);
int ore_Width = Convert.ToInt32(ore.Ore_Width);
int ore_Height = Convert.ToInt32(ore.Ore_Height) - area_Height / 2;
ore_Area.Name = "ore_Area";
ore_Area.ImageLocation = #"Data\Images\Circle.png";
ore_Area.SizeMode = PictureBoxSizeMode.StretchImage;
ore_Area.Size = new Size(Convert.ToInt32(area_Width), Convert.ToInt32(area_Height));
ore_Area.Location = new Point(Convert.ToInt32(ore_Width), Convert.ToInt32(ore_Height));
ore_Area.BackColor = Color.Transparent;
this.Controls.Add(ore_Area);
}
}
ore_Area.Parent = PIC_Map;
}
Images:
Data:
Map:
Thanks to LarsTech, it has been fixed.
Making the picturebox is moved inside the loop, this.controls.add is changed to PIC_Map.controls.add and Ore_Area.Parent = PIC_Map; is deleted.

c# setting the name of a dynamically created button so it can be called later

how do i set a buttons name when i created it in c# so i can call it later?
i have a a List of strings (Commands).
i loop over it an create a button for each item in the List.
commands.ForEach(delegate (String i)
{
Button button = new Button()
{
Name = i,
Tag = i,
MaxWidth = 50,
MaxHeight = 50,
BorderBrush = null
};
button.Click += new RoutedEventHandler(button_Click);
this.grid.Children.Add(button);
Uri resourceUri = new Uri("led_Off.png", UriKind.Relative);
StreamResourceInfo streamInfo = Application.GetResourceStream(resourceUri);
BitmapFrame temp = BitmapFrame.Create(streamInfo.Stream);
var brush = new ImageBrush();
brush.ImageSource = temp;
button.Background = brush;
});
This loop works fine until i add the line
Name = i
i am in a spot where these buttons were created and i now need to change some of there back ground images.
is there a better way to call them then by there name?
Name should be a valid string: try Name = "button" + i.ToString() (FYI, Button Name cannot be just "1", it's an invalid Name). Also, Tag =i.ToString(). Hope this may help
Don't do it, use data binding and data templating with commands instead.
There is no reason to ever create any UI elements in a loop in WPF.
Edit: Just this.

TableLayoutPanel: How can i navigate with tab key through dynamically added controls?

I have a form which contains a dynamically added TableLayoutPanel, which contains some dynamically added Labels, TextBox, CheckBox. I am obtaining exactly the visualization I would like to have, but I am struggling to get the "tab key" to work for moving from one control to the other.
I have tried to add a:
control.TabIndex = tabIndex++;
control.TabStop = true;
But this doesn't seem to have any impact...
This is the (tested) stub code:
class MyForm : Form
{
public MyForm()
{
InitializeComponent();
string[] titles = {"first","second"};
var myLayout = new TableLayoutPanel();
myLayout.AutoSize = true;
int myTabIndex = 1; //Not really necessary
int rowNumber = 0;
foreach (var title in titles)
{
var label = new Label();
label.Text = title;
myLayout.Controls.Add(label, 0, rowNumber);
var control = new TextBox();
control.TabIndex = myTabIndex++; //Not really necessary
myLayout.Controls.Add(control, 1, rowNumber);
rowNumber++;
}
this.Controls.Add(myLayout);
}
}
This is the window I get, and I am not able to navigate from first to second field using the tab key.
Update:
Applying Visual Studio 2013 Update 5 did not help.
Something must have been corrupted in my project. The best I could do is to move on with a new clean Windows Form project, and everything now is working.

Placing multiple instances of same control on form

I am making an application in winforms which shows a blueprint in a picturebox, and I need to place parts on it programmatically. These parts needs to be clickable (thus they should be a user control), and then fire the corresponding click event (clicking on a part should display information unique to that part). I could say that I want to place custom buttons on my picture. Now, of course, I need only one click event, and change the displayed information according to selection, though I don't know how to "link" this event to each created button.
I have a list of parts right next to the picturebox, and selecting a part should make the associated control to appear on the form (and deselecting it should remove it, or at least make it hidden). At first, I thought I will create one control during design, and make it appear/disappear and relocate it with each selection. The problem is, that the user should be able to select multiple parts, and the program should show all selected parts on the blueprint.
As each blueprint is different, the number of parts cannot be defined in advance. Is it possible, to create multiple instances of the same control on the run? Or is there a workaround?
If you use controls for your picture elements( you do not determine anything from coordinates of mouse click) and each picture element is associated with only one menu control, then I can propose you to use the Tag property to associate the corresponding menu controls:
public Form1()
{
InitializeComponent();
this.CreatePictureRelatedControls();
}
private void CreatePictureRelatedControls()
{
Int32 xPictureControls = 50,
yPictureControls = 50,
xAssociatedControls = 200,
yAssociatedControls = 50,
yMargin = 10;
Int32 controlWidth = 125,
controlHeight = 20;
Int32 controlCount = 3;
// ---------Associated controls-----------------
var associatedControls = new Button[controlCount];
// Loop - creating associated controls
for (int i = 0; i < associatedControls.Length; i++)
{
var associatedButton = new Button()
{
Left = xAssociatedControls,
Top = yAssociatedControls + (i * (controlWidth + yMargin)),
Width = controlWidth,
Height = controlHeight,
Text = String.Format("associated control {0}", i),
Visible = false
};
// Event handler for associated button
associatedButton.Click += (sender, eventArgs) =>
{
MessageBox.Show(((Control)sender).Text, "Associated control clicked");
};
associatedControls[i] = associatedButton;
}
// ----------------- Picture controls ---------------
var pictureControls = new Button[controlCount];
// Loop - creating picture controls
for (int i = 0; i < pictureControls.Length; i++)
{
var pictureButton = new Button()
{
Left = xPictureControls,
Top = yPictureControls + (i * (controlWidth + yMargin)),
Width = controlWidth,
Height = controlHeight,
Text = String.Format("picture part button {0}", i),
// Use of tag property to associate the controls
Tag = associatedControls[i],
Visible = true
};
// Event hadler for picture button
pictureButton.Click += (sender, eventArgs) =>
{
Control senderControl = (Control)sender;
Control associatedControl = (Control)senderControl.Tag;
associatedControl.Visible = !associatedControl.Visible;
};
pictureControls[i] = pictureButton;
}
this.Controls.AddRange(associatedControls);
this.Controls.AddRange(pictureControls);
}
P.S. If you need to associate multiple controls then you can just set Tag property to some collection:
button.Tag = new Control[] {associated[1], associated[3]};

Align dynamically added controls horizontally and vertically within a control in c# winforms

I have this program that dynamically adds pictureboxes referring to the number of president in the database. How do i put them inside the groupbox and align the pictureboxes inside the groupbox? And the groupbox should stretch if the pictureboxes are many.
I have this codes now :
private void Form1_Load(object sender, EventArgs e)
{
conn.Open();
try
{
cmd = new SqlCommand("SELECT COUNT(Position) FROM TableVote WHERE Position='" + "President" + "'", conn);
Int32 PresCount = (Int32)cmd.ExecuteScalar();
TxtPresCount.Text = PresCount.ToString();
for (int i = 0; i < PresCount; ++i)
{
GroupBox PresGB = new GroupBox();
{
PresGB.Size = new Size(491, 152);
PresGB.Location = new Point(12, 12);
PresGB.Text = "President";
this.Controls.Add(PresGB);
PresGB.SendToBack();
PictureBox PresPB = new PictureBox();
PresPB.Location = new Point(80 + (150 * i) + 20, 50);
PresPB.Size = new Size(75, 75);
PresPB.BorderStyle = BorderStyle.Fixed3D;
PresPB.ImageLocation = "imgPath";
this.Controls.Add(PresPB);
PresPB.BringToFront();
};
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
conn.Close();
}
}
I would want the pictureboxes to be inside the groupbox and align it inside.
Maybe the FlowLayoutPanel control already does what you are trying to do. Just create your picture boxes and add them to a FlowLayoutPanel instead of a GroupBox.
FlowLayoutPanel automatically arranges controls in rows and/or columns depending on the value of its FlowDirection property. Set myFlowLayoutPanel.FlowDirection = FlowDirection.TopDown to get a vertical arranged list.
If you don't want multiple rows or columns set the WrapContents property to false. You can also set the AutoScroll property to true to automatically get scrollbars if the controls don't fit.
If you prefer to have the border of a GroupBox you can still put the FlowLayoutPanel into a GroupBox.
To adjust the space between the picture boxes you can use the Margin property.
This gives you a lot of control over the layout and you don't need to calculate the control positions. Also, if the size of the FlowLayoutPanel changes everything is rearranged automatically.
UPDATE:
I have a few comments on your code:
The curly braces make this look like the syntax of an object initializer - but it isn't.
GroupBox PresGB = new GroupBox(); // this line ends with a semicolon
{
// therefore this is just a block of code not related to new GroupBox()
};
You should remove the curly braces.
The creation of the group box is inside the loop. I doubt that you want a new group box for each picture box. This is the reason why you only see a single picture. Each new group box hides all the previous ones.
You add the picture boxes to the form instead of the group box.
You use "cryptic" names. PresGB and PresPB are very likely to be swapped accidentally. Abbreviations are usually a bad choice for names.
You don't need to call SendToBack or BringToFront since you don't want the controls to overlap anyway.
I don't think GroupBox is a good choice. Of course you can make it bigger if the number of pictures increases but you are limited by the screen and you don't get scollbars if the picture boxes don't fit. Use a FlowLayoutPanel. It has all the "magic" that you are looking for.
Replace your for loop with this piece of code:
var panel = new FlowLayoutPanel();
panel.SuspendLayout(); // don't calculate the layout before all picture boxes are added
panel.Size = new Size(491, 152);
panel.Location = new Point(12, 12);
panel.BorderStyle = BorderStyle.Fixed3D;
panel.FlowDirection = FlowDirection.LeftToRight;
panel.AutoScroll = true; // automatically add scrollbars if needed
panel.WrapContents = false; // all picture boxes in a single row
this.Controls.Add(panel);
for (int i = 0; i < PresCount; ++i)
{
var pictureBox = new PictureBox();
// the location is calculated by the FlowLayoutPanel
pictureBox.Size = new Size(75, 75);
pictureBox.BorderStyle = BorderStyle.FixedSingle;
pictureBox.ImageLocation = "imgPath";
panel.Controls.Add(pictureBox);
}
panel.ResumeLayout();
You can always drop a control on your form, do what you want to do then look at the designer generated code to see how the designer does it (in the "Designer.cs" file). Behind the scenes it is loading all controls and setting all properties via code.
That being said.
Keep in mind that once you put your picturebox inside the groupbox all location coordinate are in relation to the groupbox. So "0,0" is the upper-left corner of the groupbox, not the form.
To anchor your picturebox use the following code (this is just a straight copy-paste from my designer generated code, so you can clean it up a bit):
this.PresPB.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
To dock your picture box (so it fills the entire containing control):
this.PresPB.Dock = System.Windows.Forms.DockStyle.Fill;
You also need to change this line:
this.Controls.Add(PresPB);
to this:
PresGB.Controls.Add(PresPB);

Categories

Resources