Animated Panel in C# - c#

I am trying to add a panel when a button click. My code is below and I did it. But now I am trying to put on my panel other buttons etc and when you click the first button and the panel slide in there aren't any of my new buttons.
//Constants
const int AW_SLIDE = 0X40000;
const int AW_HOR_POSITIVE = 0X1;
const int AW_HOR_NEGATIVE = 0X2;
const int AW_BLEND = 0X80000;
[DllImport("user32")]
static extern bool AnimateWindow(IntPtr hwnd, int time, int flags);
photosflag=0;
private void photosbutton_Click(object sender, EventArgs e)
{
if (photosflag == 0)
{
object O = Controller.Properties.Resources.ResourceManager.GetObject("photospressed");
photosbutton.Image = (System.Drawing.Image)O;
photosflag = 1;
int ylocation = photosbutton.Location.Y;
//Set the Location
photospanel.Location = new Point(101, ylocation);
//Animate form
AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);
}
else
{
object O = Controller.Properties.Resources.ResourceManager.GetObject("photos");
photosbutton.Image = (System.Drawing.Image)O;
photosflag = 0;
photospanel.Visible = false;
}
}
In the photos panel, I have three picture boxes. But when the panel shows up (slide-in) the picture boxes there aren't exist.

Okay - here is a really simple example that doesn't depend on the AnimateWindow API:
Add a timer control to your form. On mine, I set the interval to 10 (milliseconds). You can play with this value to smooth out the animation as necessary
I have the button and panel (not visible) on the form
I declared the following private members on the form - they are the start X position of the panel, the end position, and the number of pixels to move per increment - again, tweak to affect speed/smoothness/etc
private int _startLeft = -200; // start position of the panel
private int _endLeft = 10; // end position of the panel
private int _stepSize = 10; // pixels to move
Then on the button click, I enable the timer:
animationTimer.Enabled = true;
Finally, the code in the timer tick event makes the panel visible, moves it into place, and disables itself when done:
private void animationTimer_Tick(object sender, EventArgs e)
{
// if just starting, move to start location and make visible
if (!photosPanel.Visible)
{
photosPanel.Left = _startLeft;
photosPanel.Visible = true;
}
// incrementally move
photosPanel.Left += _stepSize;
// make sure we didn't over shoot
if (photosPanel.Left > _endLeft) photosPanel.Left = _endLeft;
// have we arrived?
if (photosPanel.Left == _endLeft)
{
animationTimer.Enabled = false;
}
}

I know this is an old thread, but there’s an easy fix for the photos not showing. The panel is originally set to visible = false, and because AnimateWindow doesn't actually show the full control, so make sure you set control.Visible = true after calling AnimateWindow.
so after the code line:
//Animate form
AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);
// just add this:
photopanel.Visible = true;
// or in one line
Photopanel.Visible = AnimateWindow(photospanel.Handle, 500, AW_SLIDE | AW_HOR_POSITIVE);

Related

Loading multiple Groupboxes in the form using the button_click event

I wish to load multiple groupboxes in the windows form application using a button_click event.
A groupbox should appear in the form each time the button is clicked.
Expected output.
I am having trouble making the location of the groupbox dynamic, as the second groupbox should be some distance away from the first groupbox. I thought of manually calculating the coordinates and using an array of points for the location, but I feel that there should be a better a way to go about it.
I have defined 'int count=0' variable to count the number of times the button is clicked. Based on that I am naming the new groupbox. But I think there is some problem in the logic used in the count++ line. It is not going after 1. Therefore I am only getting one groupbox "groupBox1". Nothing happens when I click the button again.
I appreciate your help.
Thank you
int count=0;
private GroupBox GetGroupBox(int a)
{
GroupBox groupBox = new GroupBox();
groupBox.Text = "groupBox"+(a.ToString());
groupBox.Width= 200;
groupBox.Height= 200;
groupBox.Location = new Point(50,400);
return groupBox;
}
private void button1_Click(object sender, EventArgs e)
{
count++;
this.Controls.Add(GetGroupBox(count));
}
Your question states these objectives:
Dynamically add a GroupBox based on an event (like button click).
Assign the new GroupBox location.
Pad the location with "some distance away".
You say you "feel that there should be a better a way to go about it" and there is!
Try experimenting with a FlowLayoutPanel which handles all three of these by its nature.
Here's the code I used to add and remove instances of CustomGroupBox. This is a UserControl that I added to my project, but this will work with any type of control.)
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
numericUpDownGroupboxes.ValueChanged += onGroupBoxCountChanged;
foreach (var radio in Controls.OfType<RadioButton>())
{
radio.CheckedChanged += onFlowLayoutDirectionChanged;
}
}
When the numeric up-down changes, compare the expected number of groupboxes to the current count. Alternatively, you can continue to use a button click and go straight to flowLayoutPanel.Controls.Add(...).
private void onGroupBoxCountChanged(object sender, EventArgs e)
{
// Need an int for comparison.
int
countIs = flowLayoutPanel.Controls.OfType<CustomGroupBox>().Count(),
countShouldBe = (int)numericUpDownGroupboxes.Value;
switch(countIs.CompareTo(countShouldBe))
{
case -1:
flowLayoutPanel.Controls.Add(
new CustomGroupBox
{
Name = $"groupBox{countShouldBe}",
Text = $"GroupBox {countShouldBe}",
Size = new Size(300, 150),
Margin = new Padding(10),
BackColor = Color.White,
});
break;
case 1:
Control last = flowLayoutPanel.Controls.OfType<CustomGroupBox>().Last();
flowLayoutPanel.Controls.Remove(last);
break;
}
}
The direction of the flow can also be specified.
private void onFlowLayoutDirectionChanged(object sender, EventArgs e)
{
if(radioButtonHorizontal.Checked)
{
flowLayoutPanel.FlowDirection = FlowDirection.LeftToRight;
}
else
{
flowLayoutPanel.FlowDirection = FlowDirection.TopDown;
}
}
}
Since you want to create boxes from left to right you should adjust Left: say, 1st box should have Left = 50, 2nd Left = 270, 3d Left = 490 etc.
Code:
const int deltaX = 20;
...
//TODO: check do you really want Top = 400, not, say, 20?
groupBox.Location = new Point(50 + (a - 1) * (groupBox.Width + deltaX), 400);
...
Simplified implementation can be
int count = 0;
// Let's rename the method: we actually create GroupBox, not get existing
private GroupBox CreateGroupBox(int index) => new GroupBox() {
Text = $"groupBox{index}",
Size = new Size(200, 200),
Location = new Point(50 + (index - 1) * (20 + 200), 400),
Parent = this, // Instead of Controls.Add()
};
private void button1_Click(object sender, EventArgs e) {
CreateGroupBox(++count);
}

How to make label reappear as soon as it's going out of panel width

I want to make a moving label seem nicer and smoother than just reappearing the whole thing to the left after it has all gone out of panel width .For example label 'Hello' , as soon as 'lo' goes out of bounds in the right I want it to reappear on the left. Is there any possible solution to this ?
Here's the code I have for the label now .
private void timer2_Tick(object sender, EventArgs e)
{
label5.Location = new Point(label5.Location.X + 3, label5.Location.Y);
if (label5.Location.X > this.Width)
{
label5.Location = new Point(0 - label5.Width, label5.Location.Y);
}
}
Try this, using a Label (here, named lblMarquee and a System.Windows.Forms.Timer).
The scrolling time is regulated by both the Timer.Interval and a float Field (marqueeStep).
The Timer.Tick event just calls lblMarquee.Invalidate(), causing the Label control to repaint itself.
When the scrolling text, in relation to its current position, goes beyond the limits of the Label.ClientRectangle, the section of the text which is not visible anymore is painted at start of the Label.ClientArea:
System.Windows.Forms.Timer marqueeTimer = new System.Windows.Forms.Timer();
string marqueeText = string.Empty;
float marqueePosition = 0f;
float marqueeStep = 4f;
private void form1_Load(object sender, EventArgs e)
{
marqueeText = lblMarquee.Text;
lblMarquee.Text = string.Empty;
marqueeTimer.Tick += (s, ev) => { this.lblMarquee.Invalidate(); };
marqueeTimer.Interval = 100;
marqueeTimer.Start();
}
private void lblMarquee_Paint(object sender, PaintEventArgs e)
{
var marquee = sender as Label;
SizeF stringSize = e.Graphics.MeasureString(marqueeText, marquee.Font, -1, marqueeFormat);
PointF stringLocation = new PointF(marqueePosition, (marquee.Height - stringSize.Height) / 2);
stringLength = marquee.ClientRectangle.Width - stringLocation.X;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
e.Graphics.DrawString(marqueeText, marquee.Font, Brushes.Black, stringLocation, marqueeFormat);
if (marqueePosition >= marquee.ClientRectangle.Width) marqueePosition = 0f;
if (stringSize.Width + stringLocation.X > marquee.ClientRectangle.Width) {
PointF partialStringPos = new PointF(-stringLength, (marquee.Height - stringSize.Height) / 2);
e.Graphics.DrawString(marqueeText, marquee.Font, Brushes.Black, partialStringPos, marqueeFormat);
}
marqueePosition += marqueeStep;
}
A couple of other implementations you might find useful:
How to follow the end of a text in a TextBox with no NoWrap
How to draw a string on two not adjacent areas
You need to have two label controls to do this, but it's not really that difficult. First, create a backup label and set it's properties to look like label5:
// A backup label for our scrolling label5
private Label label5_backup;
private void Form1_Load(object sender, EventArgs e)
{
label5.Text = "This is a scrolling label!";
// Set label5_backup to look like label5
label5_backup = new Label
{
Size = label5.Size,
Text = label5.Text,
Top = label5.Top,
Visible = false
};
Controls.Add(label5_backup);
timer2.Interval = 1;
timer2.Start();
}
Then, in the Tick event, as soon as our label5 starts to leave the client rectangle, set our backup label to the proper distance from the left of the form so that it starts to appear on the other side. And as soon as label5 is completely off the form, set it's location to match the backup label and then hide the backup label again.
Note that you can just set the Left property instead of creating a new Location point each time, which simplifies the code a little:
private void timer2_Tick(object sender, EventArgs e)
{
label5.Left++;
// If label5 starts to go off the right, show our backup on the left side of the form
if (label5.Right > ClientRectangle.Width)
{
label5_backup.Left = label5.Right - ClientRectangle.Width - label5.Width;
label5_backup.Visible = true;
}
// If label5 is all the way off the form now, set it's location to match the backup
if (label5.Left > ClientRectangle.Width)
{
label5.Location = label5_backup.Location;
label5_backup.Visible = false;
}
}
Also, if you want to make the scrolling smoother, only increment the Left by 1 each time and reduce the timer2.Interval to a third of what it was before (unless it's already at 1).

Stop flickering when the rich text box is resized

The following is causing a flicker when the height goes over 810.
How can I prevent this from happening?
private const int EM_GETLINECOUNT = 0xba;
[DllImport("user32",EntryPoint = "SendMessageA",CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)]
private static extern int SendMessage(int hwnd,int wMsg,int wParam,int lParam);
private void rtbScript_TextChanged(object sender,EventArgs e)
{
var numberOfLines = SendMessage(rtbScript.Handle.ToInt32(),EM_GETLINECOUNT,0,0);
this.rtbScript.Height = (rtbScript.Font.Height + 2) * numberOfLines;
if(this.rtbScript.Height>810)
{
this.rtbScript.Height = 810;
}
}
You set the Height two times, instead of one, causing the Control to repaint itself twice.
To prevent that effect store your calculation of the new height and then assign only once.
private void rtbScript_TextChanged(object sender,EventArgs e)
{
var numberOfLines = SendMessage(rtbScript.Handle.ToInt32(),EM_GETLINECOUNT,0,0);
var newHeight = (rtbScript.Font.Height + 2) * numberOfLines;
if(newHeight>810)
{
this.rtbScript.Height = 810;
}
else
{
this.rtbScript.Height = newHeight;
}
}
Try this: https://stackoverflow.com/a/3718648/5106041
The reason it flickers is because winforms doesn't do double buffering by default, that's one of the reasons WPF was created, not only it fixes these issues (we get some new ones thou) but you'll have a much richer layout system.

controls inside richtextbox down or up with scroller

Inside my richtextbox have a checkbox, when scroll down or up my richtextbox, checkbox not scrolling whit it.
CheckBox chk = new CheckBox();
chk.Name = "Chk" + i;
chk.Location = new Point(80,10+i);
chk.Text = "Save";
chk.Size = new System.Drawing.Size(20, 100);
richTextBox1.Controls.Add(chk);
i++;
Can you tell me how to solve it.
A RichTextBox cannot contain a CheckBox object. and you have set static point for location of your checkpoint
chk.Location = new Point(80,10+i)
, and it is over RichTextBox , not into that
You can handle the MouseWheel and Scroll events of your RichTextBox and use the GetScrollPos win32 function to get the new position of the scrollbar of your RichTextBox, then update the position of your CheckBox accordingly. Notice that when the MouseWheel is raised, the position of the ScrollBar is not changed to the new one immediately, it will be changed smoothly from the current to the new one. That's why we have to use a Timer to call the GetScrollPos repeatedly an periodically until the return position is the same. The effect we get is not perfect as the smoothness of the scrollbar moving but it's close to that smoothness and far better than calling the GetScrollPos once right in the MouseWheel event handler.
However in this code, I would like to use NativeWindow to hook into the message loop and fetch the messages sent to the RichTextBox, here is the code which works OK, this code is just a demo and handle the Vertical scrolling only. You can find more info on WM_HSCROLL and GetScrollPos to handle the Horizontal scrolling (it's easy because it's very much similar to the Vertical scrolling):
public partial class Form1 : Form
{
[DllImport("user32")]
private static extern int GetScrollPos(IntPtr hwnd, int nBar);
public Form1()
{
InitializeComponent();
chk.Text = ".NET pro";
chk.Parent = richTextBox1;
chk.Top = 100;//Notice the initial Top to update the position properly later.
nativeRichText.AssignHandle(richTextBox1.Handle);
//Scroll event handler for the nativeRichText
nativeRichText.Scroll += (s, e) =>
{
chk.Top = 100-e.Y;
};
//TextChanged event handler for the richTextBox1
richTextBox1.TextChanged += (s, e) =>
{
chk.Top = 100-GetScrollPos(richTextBox1.Handle, 1);
};
}
CheckBox chk = new CheckBox();
NativeRichTextBox nativeRichText = new NativeRichTextBox();
public class NativeRichTextBox : NativeWindow
{
Timer t = new Timer();
int y = -1;
public NativeRichTextBox()
{
t.Interval = 30;
t.Tick += (s, e) =>
{
int y2 = Form1.GetScrollPos(Handle, 1);//nBar =1 => Vertical bar
if (y2 == y) { t.Stop(); return; }
y = y2;
if (Scroll != null) Scroll(this, new ScrollEventArgs(0, y));
};
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x115)//WM_VSCROLL = 0x115
{
int wp = m.WParam.ToInt32();
int low = wp & 0x00ff;
if (low == 4 || low == 5)//SB_THUMBPOSITION = 4 SB_THUMBTRACK = 5
{
if (Scroll != null) Scroll(this, new ScrollEventArgs(0, wp >> 16));
}
else t.Start();
}
if (m.Msg == 0x20a)//WM_MOUSEWHEEL = 0x20a
{
y = -1;
t.Start();
}
base.WndProc(ref m);
}
public class ScrollEventArgs : EventArgs
{
public int X { get; set; }
public int Y { get; set; }
public ScrollEventArgs(int x, int y)
{
X = x;
Y = y;
}
}
public delegate void ScrollEventHandler(object sender, ScrollEventArgs e);
public event ScrollEventHandler Scroll;
}
}
Most probably you have seen around checkboxes inside comboboxes or dropdown lists.
It is possible to do it. You may try with third parties controls like this, or code your own. This topic was already discussed in another question on SO, have a look here, so we do not restart debating the same topic in this question.
What you are obtaining with your code instead, is just to put graphically one control on another but they do not interact to each other. They are two separate controls.

Switching Panels via Index Methods

I've been trying to solve my issue for quite a while and to be honest am getting nowhere. What i would like is when the user clicks the 'top' button on my panel it automatically goes to the top( and swaps with the one there.) and when they click the bottom button it automatically goes to the bottom. I'm setting the index panel manually but of course this doesnt work because its only viable for one panel (i have ten). Greatly appreciate some help in finding a method that can send the panel to the top of the stack regardless of its position.
Here is a image (basic) to help understand
Control ctrlToMove = (Control)this.bookControls[bookName];
int ctrlToMoveIndex = bookPanel.Controls.IndexOf(ctrlToMove);
int ctrlToSwapIndex = ctrlToMoveIndex - 5;
Control ctrlToSwap = bookPanel.Controls[ctrlToSwapIndex];
this.bookPanel.Controls.SetChildIndex(ctrlToMove, ctrlToSwapIndex);
this.bookPanel.Controls.SetChildIndex(ctrlToSwap, ctrlToMoveIndex);
Based on your drawing, I made a UserControl with a button on it:
void uc_ButtonClicked(object sender, EventArgs e) {
UserControl1 uc = sender as UserControl1;
if (uc != null) {
int childIndex = flowLayoutPanel1.Controls.GetChildIndex(uc);
if (childIndex > 0) {
UserControl1 ucTop = flowLayoutPanel1.Controls[0] as UserControl1;
flowLayoutPanel1.Controls.SetChildIndex(uc, 0);
flowLayoutPanel1.Controls.SetChildIndex(ucTop, childIndex);
}
}
}
According to your picture you have one control per row in panel. Thus I suggest you to use TableLayoutPanel instead of FlowLayoutPanel. Also I'd create user control for items in panel. E.g. it will have name PriorityUserControl and four buttons to increase, decrease, maximize, minimize it's 'priority' (I placed buttons horizontally just to save place on screen):
Next, create four events in this user control:
public event EventHandler PriorityMaximized;
public event EventHandler PriorityIncreased;
public event EventHandler PriorityDecreased;
public event EventHandler PriorityMinimized;
And rise appropriate event when button clicked:
private void topButton_Click(object sender, EventArgs e)
{
if (PriorityMaximized != null)
PriorityMaximized(this, EventArgs.Empty);
}
That's it. We have user control which tells whether it want to move up or down. Now add user controls to TableLayoutPanel (either manually or dynamically) and subscribe same event handlers of these four events to ALL user controls. Something like:
// create user control and attach event handlers
PriorityUserControl control = new PriorityUserControl();
control.PriorityMaximized += priorityUserControl_PriorityMaximized;
control.PriorityMinimized += priorityUserControl_PriorityMinimized;
control.PriorityIncreased += priorityUserControl_PriorityIncreased;
control.PriorityDecreased += priorityUserControl_PriorityDecreased;
// add another row to table
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
panel.RowCount = panel.RowStyles.Count;
// add control table layout panel
panel.Controls.Add(control);
panel.SetRow(control, panel.RowCount - 1);
Good. All you should do now is implement these event handlers. It's simple. E.g. decreasing priority (i.e. moving down):
private void priorityUserControl_PriorityDecreased(object sender, EventArgs e)
{
// sender is a control where you clicked Down button
Control currentControl = (Control)sender;
// get position in panel
var position = panel.GetPositionFromControl(currentControl);
// just to be sure control is not one at the bottom
if (position.Row == panel.RowCount - 1)
return;
// we want to switch with control beneath current
Control controlToSwitch = panel.GetControlFromPosition(0, position.Row + 1);
// move both controls
panel.SetRow(currentControl, position.Row + 1);
panel.SetRow(controlToSwitch, position.Row);
}
Now implementation of maximizing priority (i.e. moving to top):
private void priorityUserControl_PriorityMaximized(object sender, EventArgs e)
{
Control currentControl = (Control)sender;
var position = panel.GetPositionFromControl(currentControl);
if (position.Row == 0 || panel.RowCount < 2)
return;
Control topControl = panel.GetControlFromPosition(0, 0);
panel.SetRow(currentControl, 0);
panel.SetRow(topControl, position.Row);
}
I believe you will create rest two handlers by yourself.
The key of what you want is setting up a clear and extendable algorithm capable to deal with the different positions of the Panels. Here you have a simple code showing certain approach to this problem:
public partial class Form1 : Form
{
int[] panelLocations;
Point[] pointLocations;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
panelLocations = new int[5];
pointLocations = new Point[5];
panelLocations[1] = 1;
panelLocations[2] = 2;
panelLocations[3] = 3;
pointLocations[1] = new Point(panel1.Left, panel1.Top);
pointLocations[2] = new Point(panel2.Left, panel2.Top);
pointLocations[3] = new Point(panel3.Left, panel3.Top);
}
private void relocate(int curPanel, bool goTop)
{
int curLoc = panelLocations[curPanel];
int newLoc = curLoc - 1;
if (!goTop)
{
newLoc = curLoc + 1;
}
if (newLoc < 1) newLoc = 3;
if (newLoc > 3) newLoc = 1;
if (newLoc != curLoc)
{
int otherIndex = Array.IndexOf(panelLocations, newLoc);
panelLocations[curPanel] = newLoc;
relocatePanel(curPanel);
panelLocations[otherIndex] = curLoc;
relocatePanel(otherIndex);
}
}
private void relocatePanel(int curIndex)
{
if (curIndex == 1)
{
panel1.Location = pointLocations[panelLocations[1]];
}
else if (curIndex == 2)
{
panel2.Location = pointLocations[panelLocations[2]];
}
else if (curIndex == 3)
{
panel3.Location = pointLocations[panelLocations[3]];
}
}
private void buttonTop1_Click(object sender, EventArgs e)
{
relocate(1, true);
}
private void buttonBottom1_Click(object sender, EventArgs e)
{
relocate(1, false);
}
}
Open a new project, add 3 panels (Panel1, Panel2 and Panel3... better put different background colors) and include two buttons (buttonUp and buttonDown). This code will make the Panel1 to go up and down (by changing its position with the other panels).
The idea is pretty simple: at the start you store the positions of all the Panels in an array. In another array, you store where each panel is located every time (1 is the original position of Panel1, etc.).
It is a quite simple code which you can improve and extend as much as required, but the idea is pretty reliable and you can use it in any case: a set of fixed positions through which the panels will be moving.

Categories