Visibility and height problems when using wpf usercontrol in winforms project - c#

I have two related problems that occur in the following situation.
I have a winforms window that contains some panels. In a few of these panels there are a number of (custom) wpf user-controls.
1
if i check .Visible on the elementhost it always returns true. even though I can see that its not visible.
2
if i check .Height it will always give me the same size. even though the control itself shows a variable number of things and changes size acordingly (through Visibility.collaps);
how can I get to the correct values?
edit: code added
Okey Now I'm officialy going crazy.
If I add a few messageboxes in my code to check when and in what order the above code gets executed. When I do this everything works! but as soon as I delete the messageboxes it reverses the effect. Instead of getting bigger when needed it gets smaller en vice versa....
wtf wpf!
private Size bereken_panel(Panel P)
{
Size Sz = new Size();
int tmp_H = 42;
foreach (Control SC in P.Controls)
{
if (SC is SplitContainer)
{
if (SC.Visible)
{
tmp_H += SC.Height;
}
}
else if (SC is System.Windows.Forms.Integration.ElementHost)
{
if ((SC as System.Windows.Forms.Integration.ElementHost).Child.Visibility == System.Windows.Visibility.Visible)
{
tmp_H += (int)(SC as System.Windows.Forms.Integration.ElementHost).Child.RenderSize.Height;
}
}
}
// tmp_H = 42 + n_showed * 25;
if (tmp_H < 65)
{
tmp_H = 65;
}
Sz.Height = tmp_H;
Sz.Width = 432;
return Sz;
}
so this is after some extra modification
to clarify where the TopLeft point is.
int p_x_links = panel1.Width / 2 - 436;
int p_x_rechts = panel1.Width / 2 + 4;
//links
p_contact_gegevens.Size = bereken_panel(p_contact_gegevens);
p_telnrs.Location = new Point(p_x_links, p_contact_gegevens.Size.Height + p_contact_gegevens.Location.Y + 8);
p_telnrs.Size = bereken_panel(p_telnrs);
p_bezoekadres.Location = new Point(p_x_links, p_telnrs.Size.Height + p_telnrs.Location.Y + 8);
p_bezoekadres.Size = bereken_panel(p_bezoekadres);
//rechts
p_administratie.Size = bereken_panel(p_administratie);
p_postadres.Location = new Point(p_x_rechts, p_administratie.Size.Height + p_administratie.Location.Y + 8);
p_postadres.Size = bereken_panel(p_postadres);

Most probabbly the control is overlapped by some other control. Visibility property in this case can be even True.
Try to use ActualWidth instead.
EDIT
Sorry, the example is on ActualWidth, but you are asking for Height. Concept is the same btw.

Related

Adding multiple PictureBox to a C# Form

I am trying to add several pictures (using PictureBox) of music notes to a Music Staff Form.
In a certain Mouse Event (MouseUp), a Music Note is created, and a music note should appear on the screen. However, only when the first note is created does the image appear on the screen. For every other note created after the first one, the image does not show up.
In the below method, the Music Note is created:
private void Key_MouseUp(object sender, MouseEventArgs e)
{
foreach (MusKey mk in panel1.Controls)
{
if (sender == mk) //true for the specific key pressed on the Music Keyboard
{
if (e.Button == MouseButtons.Left)
{
timer1.Enabled = false;
sp.Stop();
string bNoteShape = null;
// ticks -> milliseconds
if (count >= 16)
{
bNoteShape = "SemiBreve";
duration = 1024;
}
else if (count >= 8 && count <= 15)
{
bNoteShape = "DotMinim";
duration = 768;
}
else if (count >= 4 && count <= 7)
{
bNoteShape = "Crotchet";
duration = 384;
}
else if (count >= 2 && count <= 3)
{
bNoteShape = "Quaver";
duration = 128;
}
else
{
bNoteShape = "Semi-Quaver";
duration = 90; //63 is too short
}
MusicNote mn = new MusicNote(mk.getMusicNote(), duration, bNoteShape, mk.getNote());
Notes.Add(mn);
mn.picturebox1.Location = new Point(xLoc1, yLoc1);
panel2.Controls.Add(mn.picturebox1); //adding MusicNote component to MusicStaff (panel2) collection
}
}
}
}
When the Note is created, it is sent to the MusicNote constructor:
public MusicNote(int iNotepitch, int iDuration, String iBnoteShape, String iNote)
{
notepitch = iNotepitch;
noteduration = iDuration;
noteshape = iBnoteShape;
note = iNote;
picturebox1.ImageLocation = NoteImage_path + noteshape + ".bmp";
picturebox1.BackColor = Color.Transparent;
picturebox1.Location = new Point(xLoc, yLoc);
xLoc = xLoc + 15;
}
I have tried initializing the location (and incrementing xLoc) both through the constructor, and also in the form itself in method Key_MouseUp, but neither seems to make a difference.
The logic seems to be correct, as the picture of the first music note always loads, but I cannot understand why every other note after the first does not show up on my screen.
Any help will be appreciated, thanks!
Edit: perhaps maybe there is another alternative to PictureBox that I could use to store the music notes?
well it looks like your not initializing a new "picturebox1"
picturebox1 = new PictureBox(); //Add this
picturebox1.ImageLocation = NoteImage_path + noteshape + ".bmp";
edit:
Ok in general we need a few things to add a control to a form.
the new Control (look like you have that) 2
Adding it to the parent control (looks like you have that)
panel2.Controls.Add(mn.picturebox1); //adding MusicNote component to MusicStaff (panel2) collection
redraw the control --
this can happen automatically when you resize the form or you can call refresh(on the panel) after the new control is added to the panel.
panel2.Controls.Add(mn.picturebox1);
panel2.Refresh();

Adding labels dynamically to a form: Labels not visible when trying to add more labels

I use this code to add some labels to a windows form in c#:
Label[] lbl = new Label[temp+1];
for (int i = 0; i <= temp; i++) {
lbl[i] = new Label();
lbl[i].Text = "" + i;
lbl[i].Location = new Point(30 + (i * unit), 380);
lbl[i].Visible = true;
this.Controls.Add(lbl[i]);
}
There is not a serious problem but my code works for temps less than 5 and for temps greater than 5 it shows only the first one.
What you think? where is the problem?
Make the labels automatically adjust their size to their content by setting their AutoSize property to true:
lbl[i] = new Label();
lbl[i].Text = "" + i;
lbl[i].Location = new Point(30 + (i * unit), 380);
lbl[i].Visible = true;
lbl[i].AutoSize = true;
this.Controls.Add(lbl[i]);
Without this, the labels have a fixed size. When this fixed size is greater than unit, the labels overlap and hide each other's text. With more labels to add, unit becomes smaller and then smaller than the default width of the labels when temp is ≥ 5.
Alternatively, you could set the labels' width to unit to make sure that they do not overlap.

How do you make AutoSize happen immediately?

The problem
I'm dynamically adding Buttons to the WinForm. As I do so, I'm repositioning existing Buttons to prevent overlap. The AutoSize property is being used to automatically set Width.
For longer text (that pushes Buttons beyond their default Width), the below code doesn't work.
For example:
b.Width is 75 before AutoSize is set
b.Width is 75 after AutoSize is set
When shifting other Buttons, it shifts them by b.Width + buffer = 83
However after addButton()completes, the AutoSize kicks in and sets the width to 150, overlapping the next Button which is only 83 pixels away instead of 158.
AutoSize appears to change the size of the control too late for it to be of use. How can I make it happen immediately?
Attempt 1 - Code
public void addButton(string text)
{
const int buffer = 8;
//Construct new button
Button b = new Button();
b.Text = text;
b.AutoSize = true;
b.Location = new Point(0, 0);
//Shift over all other buttons to prevent overlap
//b.Width is incorrect below, because b.AutoSize hasn't taken effect
for (int i = 0; i < Controls.Count; i++)
if (Controls[i] is Button)
Controls[i].Location = new Point(Controls[i].Location.X + b.Width + buffer, Controls[i].Location.Y);
Controls.add(b);
}
Attempt 2
Searched Google and StackOverflow for the following:
c# autosize immediately
c# autosize fast
c# autosize not working
Attempt 3
Asking here.
Last Resort
If nothing else works, a timer could be set to reposition Buttons on each tick. However this is very sloppy design, and doesn't aid in learning the intricacies of AutoSize. I'd like to avoid this workaround if possible.
The AutoSize and AutoSizeMode mode are applied only when the control is parented to the another control or form.
So invoke first
Controls.Add(b);
Now the b.Size will the adjusted accordingly and can be used in the calculations.
Alternatively, instead of Size property you can use the GetPreferredSize method to get the correct size without actually applying AutoSize and use it inside the calculations:
var bSize = b.GetPreferredSize(Size.Empty);
//Shift over all other buttons to prevent overlap
//b.Width is incorrect below, because b.AutoSize hasn't taken effect
for (int i = 0; i < Controls.Count; i++)
if (Controls[i] is Button)
Controls[i].Location = new Point(Controls[i].Location.X + bSize.Width + buffer, Controls[i].Location.Y);
The FlowLayoutPanel control does this work for you.
Place one on your form and try adding buttons in the following manner:
Button b = new Button();
b.AutoSize = true;
b.Text = text;
flowLayoutPanel1.SuspendLayout();
flowLayoutPanel1.Controls.Add(b);
flowLayoutPanel1.Controls.SetChildIndex(b, 0);
flowLayoutPanel1.ResumeLayout();
You can subscribe to the Resize event of the last button added. This will allow you to accurately change the locations of all of the buttons because now all of the buttons have been AutoSized.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var button1 = NewButton(0);
button1.Location = new Point(10, 10);
var button2 = NewButton(1);
button2.Location = new Point(button1.Right, 10);
var button3 = NewButton(2);
button3.Location = new Point(button2.Right, 10);
button3.Resize += (s, e) =>
{
button2.Location = new Point(button1.Right, 10);
button3.Location = new Point(button2.Right, 10);
};
Controls.Add(button1);
Controls.Add(button2);
Controls.Add(button3);
}
private Button NewButton(int index)
{
return new Button()
{
Text = "ButtonButtonButton" + index.ToString(),
AutoSize = true
};
}
}

Custom control using ScrollableControl with fixed position text

I'm building custom user control that will be used to display tiles map, as base class I've chosen ScrollableControl, because I want to have scrollbars in my control.
I've successfully created paint logic that is responsible for painting only needed elements.
Now I'm trying to add static text that will be always visible in same place (in my case white box with red text in top left corner):
This isn't clearly visible on above gif, but that white box blinks and jumps a bit when I scroll using mouse or scrollbars.
My question is how should I change my code to have scrollable content and fixed position content on top of that scrollable content?
Is ScrollableControl good choice as base class?
Below is my code:
class TestControl : ScrollableControl
{
private int _tileWidth = 40;
private int _tileHeight = 40;
private int _tilesX = 20;
private int _tilesY = 20;
public TestControl()
{
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.Opaque, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
ResizeRedraw = true;
AutoScrollMinSize = new Size(_tilesX * _tileWidth, _tilesY * _tileHeight);
Scroll += (sender, args) => { Invalidate(); };
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle);
e.Graphics.TranslateTransform(AutoScrollPosition.X, AutoScrollPosition.Y);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var offsetX = (AutoScrollPosition.X * -1) / _tileWidth;
var offsetY = (AutoScrollPosition.Y * -1) / _tileHeight;
var visibleX = Width / _tileWidth + 2;
var visibleY = Height / _tileHeight + 2;
var x = Math.Min(visibleX + offsetX, _tilesX);
var y = Math.Min(visibleY + offsetY, _tilesY);
for (var i = offsetX; i < x; i++)
{
for (var j = offsetY; j < y; j++)
{
e.Graphics.FillRectangle(Brushes.Beige, new Rectangle(i*_tileWidth, j*_tileHeight, _tileWidth, _tileHeight));
e.Graphics.DrawString(string.Format("{0}:{1}", i, j), Font, Brushes.Black, new Rectangle(i * _tileWidth, j * _tileHeight, _tileWidth, _tileHeight));
}
}
using (var p = new Pen(Color.Black))
{
for (var i = offsetX + 1; i < x; i++)
{
e.Graphics.DrawLine(p, i*_tileWidth, 0, i*_tileWidth, y*_tileHeight);
}
for (var i = offsetY + 1; i < y; i++)
{
e.Graphics.DrawLine(p, 0, i*_tileHeight, x*_tileWidth, i*_tileHeight);
}
}
e.Graphics.FillRectangle(Brushes.White, AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1, 35, 14);
e.Graphics.DrawString("TEST", DefaultFont, new SolidBrush(Color.Red), AutoScrollPosition.X * -1, AutoScrollPosition.Y * -1);
}
}
EDIT:
I've searched a bit and found UserControl that has similar functionality - https://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView and after reading a bit more on control's author blog http://objectlistview.sourceforge.net/cs/blog1.html#blog-overlays I found out that he is using Transparent Form that is positioned on top of control.
I really would like to avoid that, but still have overlay on top of my control.
You are doing battle with a Windows system option named "Show window content while dragging". Always turned on by default, this web page shows how to turn it off.
Solves the problem, but it is not something you can rely on since it affects all scrollable window in all apps. Demanding that the user turns it off for you is unrealistic, users like this option so they'll just ignore you. That they did not provide an option to turn it off for a specific window was a pretty major oversight. It is an okay solution in a kiosk app.
Briefly, the way the option works is that Windows itself scrolls the window content with the ScrollWindowEx() winapi function. Using a bitblt of the window content to move pixels and only generating a paint request for the part of the window that was revealed by the scroll. Usually only a few lines of pixels, so completes very fast. Problem is, that bitblt moves your fixed pixels as well. The repaint moves them back. Pretty noticeable, the human eye is very sensitive to motion like that, helped avoid being lion lunch for the past million years.
You'll have to take the sting out of ScrollWindowsEx(), preventing it from moving pixels even though you can't stop it from being called. That takes a heavy sledgehammer, LockWindowUpdate(). You'll find code in this post.
using System.Runtime.InteropServices;
...
protected override void OnScroll(ScrollEventArgs e) {
if (e.Type == ScrollEventType.First) {
LockWindowUpdate(this.Handle);
}
else {
LockWindowUpdate(IntPtr.Zero);
this.Update();
if (e.Type != ScrollEventType.Last) LockWindowUpdate(this.Handle);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool LockWindowUpdate(IntPtr hWnd);
Not that pretty, using a separate Label control ought to start sounding attractive.
can you just add a label to that control(on top), in other words - cant you use it as panel?

Programmatic Insertion of Controls into a Form

I have a for loop which I'm attempting to use to generate and lay out the contents of a Form.
This is what I've gotten to so far:
public void RefreshSkillDialog()
{
Point nPt = new Point(25, 25);
for (int x = 0; x < Enum.GetNames(typeof(Character.Skill)).Length; x++ )
{
GenerateFields(x,nPt);
Console.Write(this.Controls.Count + "\n");
Console.WriteLine(this.Controls[x].ToString() + "\n");
}
}
public void GenerateFields(int it, Point pt)
{
Label tLbl = new Label();
tLbl.Location = new Point(pt.X + (it * _vSpace), pt.Y);
tLbl.Name = Enum.GetName(typeof(Character.Skill), it);
tLbl.Text = this.Controls.Count.ToString();
this.Controls.Add(tLbl);
}
_vSpace is an integer initialized to 10 in the constructor.
The result of this code is:
I thought the issue was in reusing the tLbl variable, but as far as I can tell, it should work fine since I'm re-initializing it at the beginning of every iteration.
When you create a new Label, it is 100px wide by default.
You're changing the location (x-coordinate) of each new Label, but not by enough. Each new Label is overlapping the previous one, such that's it's covering up the text.
You can fix this by setting AutoSize = true on each Label:
Label tLbl = new Label();
tLbl.AutoSize = true;
...
Or by resizing it so that it's not as wide:
Label tLbl = new Label();
tLbl.Size = new Size(10, 23);
...
You might also consider just using a FlowLayoutPanel, since it will handle the layout for you. Add each new control to it, and don't bother with setting a Location.
Label tLbl = new Label();
tLbl.Name = Enum.GetName(typeof(Character.Skill), it);
tLbl.Text = flowLayoutPanel1.Controls.Count.ToString();
flowLayoutPanel1.Controls.Add(tLbl);
If you do that, you'll have to change your Console.WriteLine statement in the other method too:
Console.WriteLine(flowLayoutPanel1.Controls[x].ToString() + "\n");

Categories