I have an application that prints how many bar codes you want, but if the amount of bar codes is bigger than the size of the PrintDocument it doesn't jump to the next page.
I'd like to know how can I add more pages or write in the next page of a PrintDocument.
I'm using a PrintPreview to display the PrintDocument in this Windows Form.
If you hookup the OnPrintPage event you can tell the PrintDocument if it needs to add another page on the PrintPageEventArguments.
IEnumerator items;
public void StartPrint()
{
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage);
items = GetEnumerator();
if (items.MoveNext())
{
pd.Print();
}
}
private void pd_PrintPage(object sender, PrintPageEventArgs ev)
{
const int neededHeight = 200;
int line =0;
// this will be called multiple times, so keep track where you are...
// do your drawings, calculating how much space you have left on one page
bool more = true;
do
{
// draw your bars for item, handle multilple columns if needed
var item = items.Current;
line++;
// in the ev.MarginBouds the width and height of this page is available
// you use that to see if a next row will fit
if ((line * neededHeight) < ev.MarginBounds.Height )
{
break;
}
more = items.MoveNext();
} while (more);
// stop if there are no more items in your Iterator
ev.HasMorePages = more;
}
Related
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);
}
I am printing a panel with all its controls Inside. My problem is that I want to make a counter Inside this panel and increase its value for each pages printed. I tried incrementing the counter Inside my PrintPage event but this isn't working. Would it be possible to add multiple printpage events with increased counter for every page ? Here's my not working code. Thanks for giving me advices about how I could do this printing...
private void PrintPage(object o, PrintPageEventArgs e)
{
try
{
if (File.Exists(toPrint) && nbrPrint < nbrPages)
{
Rectangle m = panel1.ClientRectangle;
Bitmap imaag = new Bitmap(m.Width, m.Height);
panel1.DrawToBitmap(imaag, m);
e.Graphics.DrawImage(imaag, e.MarginBounds);
nbrPrint++;
compteurPrint++;
cpt.Text = compteurPrint.ToString();
}
}
catch (Exception)
{
}
}
nbrPrint is the pages printed, nbrPages the number of pages asked to print, compteurPrint the value I need to print in the page (which needs to be incremented), cpt is the label (Inside panel1) in which I show compteurPrint.
Since I do this:
pd.PrintPage += PrintPage;
pd.Print();
Can I do:
while (nbrPrint < nbrPages)
{
pd.PrintPage += PrintPage;
compteurPrint++;
}
pd.Print();
I think it would be the same result as what I'm currently doing…
Thanks for help !
You need to set the HasMorePages property on PrintPageEventArgs to tell it that there are more pages to print.
private void PrintPage(object sender, PrintPageEventArgs e)
{
try
{
if (File.Exists(toPrint) && nbrPrint < nbrPages)
{
Rectangle m = panel1.ClientRectangle;
Bitmap imaag = new Bitmap(m.Width, m.Height);
panel1.DrawToBitmap(imaag, m);
e.Graphics.DrawImage(imaag, e.MarginBounds);
nbrPrint++;
compteurPrint++;
cpt.Text = compteurPrint.ToString();
e.HasMorePages = true; // Set this to true as long as there are more pages to print. It defaults to false.
}
}
catch (Exception)
{
}
}
Checkout the Microsoft documentation for an example: PrintDocument.PrintPage
I've found some issues on this but nothing that would help me in my logic. I have following code (showing not all because it would be too much):
public int AreaCounter { get; set; } = 0;
public PrintDocument pd { get; set; }
public void PrintCharts(DataTable dt)
{
foreach (DataRow row in dt.Rows)
{
//after a couple of rows - here is code for creating
//a new chartArea and binding the points to the series
//plus binding that series to the area
if(// two chartAreas have been created with each one having a chart)
{
PrintChartControl();
}
AreaCounter++;
}
}
private void PrintChartControl()
{
pd.PrintPage += pd_PrintPage;
pd.Print();
}
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
if (AreaCounter < 11) //12 ChartAreas have to be created
e.HasMorePages = true;
else
e.HasMorePages = false;
var myRec = e.MarginBounds;
Container.chart1.Printing.PrintPaint(e.Graphics, myRec);
}
Now what I want to do:
I loop through a DataTable. Every few lines I create a new ChartArea, binding some values from this lines to a Series and bind this to the ChartArea. I'm actually drawing to ChartAreas to my Chart Control. Every two ChartAreas I print the Control, clean it, and draw the next two ChartAreas (there are 12 at the end - so 6 times drawing to the Chart Control). This works but I want to achieve the following:
I want to add a new page for every Chart Control to my print event so that I have 6 pages at the end and then print this to one file (pdf). Somehow it gets into an infinitive loop in the printPage Event because of the e.HasMorePages property. How does this work with the chart?
Thank you very much!
You better have the PrintDocument class drive your data, instead of you trying to drive the PrintDocument. The PrintDocument class will raise a PrintPage event, it is our task to provide the data for that single page. The data goes onto the Graphics instance provided in the PrintPageEventArgs. If we want to print another page we make sure HasMorePages is true, the printdocument instance will call PrintPage again and we provide the data for the next page etc. When we have no more data and don't want more pages to be printed, we set HasMorePagesto false.
Based on your code I created the following example that I expect to be applicable to your case.
I assumed the Rows in your DataTable are the main source to determine if and how many pages you need to print. If it is not based on that, you can create an Enumerator for a different collection/array, the mechanisms remain the same.
// the reference to the enumerator for the DataRows
IEnumerator rows;
private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
var dataTable = Load();
rows = dataTable.Rows.GetEnumerator();
}
private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// no rows, no glory
if (rows == null) return;
// keep track of stuff
var chartsOnthisPage = 0;
var yPos = 20f;
// loop for what needs to go on this page, atm it prints 2 charts
// as long as there are rows
// the enumerator is moved to the next record ...
while ((e.HasMorePages = rows.MoveNext()))
{
// ... get hold of that datarow
var currentRow = (DataRow)rows.Current;
// print
e.Graphics.DrawString(currentRow[0].ToString(), Font, Brushes.Black, 0, yPos);
// keep track where we are
yPos = yPos + 40;
// do what ever is need to print the chart fro this row
GenerateChart(currentRow);
// print the chart
chart1.Printing.PrintPaint(e.Graphics, new Rectangle(0, (int)yPos, 200, 200));
// position tracking
yPos = yPos + 200;
// optionaly break here if we reached the end of the page
// keep track
chartsOnthisPage++;
if (chartsOnthisPage > 1) break; // HasMorePages is set
}
}
private void printDocument1_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
// clean up;
rows = null;
}
I have two helper methods, one to create a DataTable with rows and one to generate some interesting chart.
private DataTable Load()
{
var dt = new DataTable();
dt.Columns.Add("chart");
for(int r=0; r<10; r++)
{
var rw = dt.NewRow();
rw[0] = Guid.NewGuid().ToString("N");
dt.Rows.Add(rw);
}
return dt;
}
Random rnd = new Random();
private void GenerateChart(DataRow row)
{
chart1.Series.Clear();
chart1.Series.Add("data");
var mx = rnd.Next(rnd.Next(10) + 3);
for (int x = 0; x < mx; x++)
{
chart1.Series[0].Points.Add(x, rnd.Next(100));
}
}
When I run this with a preview control this is what I get:
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.
i am able to put a video in my windows form.
my question is how do i make it when it finishes playing the video,it starts to play another video? meaning like in a sequence. after it finishes, play another video.
so far i have manage to play a video and it just loops the video.
any ideas?
this is my code so far:
public partial class Form1 : Form
{
Video video;
public Form1()
{
InitializeComponent();
Initializevid1();
}
public void Initializevid1()
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video("C:\\Users\\Dave\\Desktop\\WaterDay1.wmv");
// set the panel as the video object’s owner
video.Owner = viewport;
// stop the video
video.Play();
video.Ending +=new EventHandler(BackLoop);
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
}
private void BackLoop(object sender, EventArgs e)
{
//video.CurrentPosition = 0;
}
When playing video sequences in AudioVideoPlayback:
Create a list of videos to be displayed (using file paths), preferably in a listbox.
Use an integer to get file path from the listbox.items index.
Ensure video is disposed before loading next video.
Increment integer every time a video is played.
Use an if statement to see if it is the end of the sequence.
As a personal preference (not sure how much difference it makes) I would resize video before playing
So from your code: (haven't tested this, but in theory, I think it should work)
public partial class Form1 : Form
{
Video video;
Int SeqNo = 0;
public Form1()
{
InitializeComponent();
Initializevid1();
}
public void Initializevid1()
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video(listbox1.Items[SeqNo].Text);
// set the panel as the video object’s owner
video.Owner = viewport;
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
// stop the video
video.Play();
// start next video
video.Ending +=new EventHandler(BackLoop);
}
private void BackLoop(object sender, EventArgs e)
{
// increment sequence number
SeqNo += 1; //or '++SeqNo;'
//check video state
if (!video.Disposed)
{
video.Dispose();
}
//check SeqNo against listbox1.Items.Count -1 (-1 due to SeqNo is a 0 based index)
if (SeqNo <= listbox1.Items.Count -1)
{
// store the original size of the panel
int width = viewport.Width;
int height = viewport.Height;
// load the selected video file
video = new Video(listbox1.Items[SeqNo].Text);
// set the panel as the video object’s owner
video.Owner = viewport;
// resize the video to the size original size of the panel
viewport.Size = new Size(width, height);
// stop the video
video.Play();
// start next video
video.Ending +=new EventHandler(BackLoop);
}
}
You can use the same video object created earlier to open the second video file in the BackLoop() function.
So, the code should look like something this:
private void BackLoop(object sender, EventArgs e)
{
video.Open("C:\\Users\\Dave\\Desktop\\WaterDay2.wmv", true);
}
I used this post to adapt it to my needs and this was my solution:
Video _SegaVideo;
Video _IntroVideo;
public _FrmMain()
{
InitializeComponent();
_SegaVideo = new Video(#"video\SEGA.AVI");
_SegaVideo.Owner = _VideoPanel;
_SegaVideo.Play();
_SegaVideo.Ending += new EventHandler(_SegaVideoEnding);
}
private void _SegaVideoEnding(object sender, EventArgs e)
{
_IntroVideo = new Video(#"video\INTRO.AVI");
_IntroVideo.Owner = _VideoPanel;
_IntroVideo.Play();
}