I learn how to do this based on this tutorial : http://www.nbdtech.com/Blog/archive/2009/04/20/wpf-printing-part-2-the-fixed-document.aspx
This is the method that my Print Button fired when clicked :
PrintManager _pm;
private void btnPrint_Click(object sender, RoutedEventArgs e)
{
_pm = new PrintManager();
List<Canvas> pages = new List<Canvas>();
pages.Add(cnv);
_pm.Print(pages);
}
And this is my PrintManager.cs (_pm) :
private const double PAGE_WIDTH = 793.92; //8.27' * 96px
private const double PAGE_HEIGHT = 1122.24; //11.69' * 96px
private FixedDocument _document;
private List<FixedPage> _listOfPages;
private PageContent _tempPageContent;
private FixedPage _tempPage;
public PrintManager()
{
}
public void Print(List<Canvas> pages)
{
PrintDialog pd = new PrintDialog();
Canvas temp;
if (pd.ShowDialog() == true)
{
_document = new FixedDocument();
_document.DocumentPaginator.PageSize = new System.Windows.Size(PAGE_WIDTH, PAGE_HEIGHT);
_listOfPages = new List<FixedPage>();
for (int i = 0; i < pages.Count; i++)
{
_listOfPages.Add(new FixedPage());
_tempPage = _listOfPages[_listOfPages.Count - 1];
_tempPage.Width = PAGE_WIDTH;
_tempPage.Height = PAGE_HEIGHT;
_tempPage.Children.Add(pages[i]); //THIS IS THE PROBLEM LINE
_tempPageContent = new PageContent();
((IAddChild)_tempPageContent).AddChild(_tempPage);
_document.Pages.Add(_tempPageContent);
}
pd.PrintDocument(_document.DocumentPaginator, "Docdoc");
}
}
It turns out that there's an error from the problem line (_tempPage.Children.Add(pages[i]);) line which says :
Specified element is already the logical child of another element. Disconnect it first.
Any idea why?
Even removing the element from private List<FixedPage> _listOfPages; before it's added into the page list didn't help (of course I tried it outside the for loop) with just 1 canvas in the list.
P.S. I need it to be multi-page-able, since my document is usually long (it's a musical scores).
P.S.S. Even printing a canvas with no children manually added, the error was still the same.
P.S.S.S. Another simpler algorithm, even if it's different, is acceptable.
Thanks.
It seems that I need to "copy" the canvas content into a new canvas class before sending it to printer.
And clear it from the old canvas. Is anyone can do it better?
Related
I'm currently struggling with a button I want to add dynamically inside a RichTextBox.
The main issue is that I can't seem to get the location down properly (Location X & Y) also that the Text adjusts with Form Changes but the location of the Buttons won't.
The concept behind this is that I want to be able to write RTF Documents and include Button Clicks for certain Functions (Copy for now) without having to adjust the program every time.
My idea was to have a very specific Pattern -> [Copy|WHAT-TO-COPY] and replace every occurrence of this with a [C] Button in the application.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
LoadRTF();
AddButtons();
}
private void LoadRTF()
{
richTextBox1.LoadFile("RTF Path");
richTextBox1.ReadOnly = true;
}
private void AddButtons()
{
String Text = richTextBox1.Rtf;
richTextBox1.ReadOnly = false;
foreach (Match CopyMatch in Regex.Matches(Text, #"\[Copy\|.*\]", RegexOptions.IgnoreCase))
{
string CopyString = CopyMatch.Value;
var CopyPosition = richTextBox1.GetPositionFromCharIndex(richTextBox1.Find(CopyString));
Button Copy = new Button
{
Text = "Copy",
Size = new Size(20, 23),
Location = new Point(CopyPosition.X, CopyPosition.Y)
};
Copy.Click += (_, args) =>
{
Clipboard.SetText(CopyString.Substring(6, CopyString.Length - 7));
};
richTextBox1.SelectionStart = richTextBox1.Find(CopyString);
richTextBox1.SelectionLength = CopyString.Length;
richTextBox1.SelectedText = "";
richTextBox1.Controls.Add(Copy);
}
richTextBox1.ReadOnly = true;
}
}
The first issue is that this method apparently doesn't work to get the proper X,Y values for the Button Creation. It seems the values don't align with the found PositionFromChar(Index).
Am I headed in the entirely wrong direction is this a proper start? What am I missing?
Also, I'm still completely new to programming so sorry if this is a total mess.
I am trying to reproduce the operation of the Control Expander WPF, or as shown in the menu of Outlook, Vertical Web Menu etc., since in WindowsForms this control does not exist. Here I leave the sample code: Menu_Expader.zip link GoogleDrive.
I have managed to do it using the following controls:
Panels
FlowLayoutPanel
1 Time Control
Button Vectors
Labels Vectors ...
This works perfectly, but it happens that to each panel I must establish a
Maximum Size and Minimum Size therefore every time I add an item inside I must modify the size of the panel where I add it, and the item are very close to each other is a bit annoying for the user's vision.
Example this is what I currently have:
EDIT
Code Sample:
// The state of an expanding or collapsing panel.
private enum ExpandState
{
Expanded,
Expanding,
Collapsing,
Collapsed,
}
// The expanding panels' current states.
private ExpandState[] ExpandStates;
// The Panels to expand and collapse.
private Panel[] ExpandPanels;
// The expand/collapse buttons.
private Button[] ExpandButtons;
// Initialize.
private void Form1_Load(object sender, EventArgs e)
{
// Initialize the arrays.
ExpandStates = new ExpandState[]
{
ExpandState.Expanded,
ExpandState.Expanded,
ExpandState.Expanded,
};
ExpandPanels = new Panel[]
{
panModule1,
panModule2,
panModule3,
};
ExpandButtons = new Button[]
{
btnExpand1,
btnExpand2,
btnExpand3,
};
// Set expander button Tag properties to give indexes
// into these arrays and display expanded images.
for (int i = 0; i < ExpandButtons.Length; i++)
{
ExpandButtons[i].Tag = i;
ExpandButtons[i].Image = Properties.Resources.expander_down;
}
}
// Start expanding.
private void btnExpander_Click(object sender, EventArgs e)
{
// Get the button.
Button btn = sender as Button;
int index = (int)btn.Tag;
// Get this panel's current expand
// state and set its new state.
ExpandState old_state = ExpandStates[index];
if ((old_state == ExpandState.Collapsed) ||
(old_state == ExpandState.Collapsing))
{
// Was collapsed/collapsing. Start expanding.
ExpandStates[index] = ExpandState.Expanding;
ExpandButtons[index].Image = Properties.Resources.expander_up;
}
else
{
// Was expanded/expanding. Start collapsing.
ExpandStates[index] = ExpandState.Collapsing;
ExpandButtons[index].Image = Properties.Resources.expander_down;
}
// Make sure the timer is enabled.
tmrExpand.Enabled = true;
}
// The number of pixels expanded per timer Tick.
private const int ExpansionPerTick = 7;
// Expand or collapse any panels that need it.
private void tmrExpand_Tick(object sender, EventArgs e)
{
// Determines whether we need more adjustments.
bool not_done = false;
for (int i = 0; i < ExpandPanels.Length; i++)
{
// See if this panel needs adjustment.
if (ExpandStates[i] == ExpandState.Expanding)
{
// Expand.
Panel pan = ExpandPanels[i];
int new_height = pan.Height + ExpansionPerTick;
if (new_height >= pan.MaximumSize.Height)
{
// This one is done.
new_height = pan.MaximumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
else if (ExpandStates[i] == ExpandState.Collapsing)
{
// Collapse.
Panel pan = ExpandPanels[i];
int new_height = pan.Height - ExpansionPerTick;
if (new_height <= pan.MinimumSize.Height)
{
// This one is done.
new_height = pan.MinimumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
}
// If we are done, disable the timer.
tmrExpand.Enabled = not_done;
}
I want to get a result similar to this - Bootstrap Menu Accordion:
Imitate that operation panels expand according to the quantity of item that it contains as long as it does not protrude from the screen, in which case it will show the scroll bar. I know there are software that provide custom controls like DVexpress, DotNetBar Suite among others, but they are Licensed Software I do not want to use it illegally pirate. Can you help me optimize it or create it in another way?
Environment: Visual Studio 2010 & .NET NetFramework 4.
The original question I made it in StackOverFlow in Spanish.
Modulo (Module)
Menu Principal (Main menu)
Mantenimientos (Maintenance)
Procesos (Processes)
Consultas (Queries)
Reportes (Reports)
Note: If someone speaks Spanish and English and can do a better translation, please edit the question. (Excuse the advertising on the image, I recorded the screen with a software trial version).
newbie programmer here after hours of searching has left me stumped.
I'm having trouble with referencing a control inside a tab created at RunTime with a button press. Basically what I have is a tabletop RPG calculator, using a Windows Form, that has a tabControl holding tab pages, with each tab page holding user-inputted stats for that individual enemy to be used in calculations.
The problem is that I want the user to be able to click a button to generate a new enemy tab page. Here is my code for generating an enemy tab page with a TextBox.
int enemyNumber = 0;
// Creates a new Enemy Tab
private void button2_Click_1(object sender, EventArgs e)
{
// Create a new TabPage
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
// Add Enemy Name Box
var newEnemyNameBox = new TextBox()
{
Name = "enemyNameBox" + enemyNumber,
Text = "",
Location = new Point(127, 11),
Size = new Size(133, 20)
};
// Add the controls to the new Enemy tab
newTabPage.Controls.Add(newEnemyNameBox);
// Add the TabPage to the TabControl
tabControl1.TabPages.Add(newTabPage);
// Increases the enemy's "reference number" by 1
// So that enemy tabs will be generated in order enemyTab0, enemyTab1, etc.
enemyNumber += 1;
}
This all works nicely. Unfortunately, after this point things have gotten ugly. I need to reference that TextBox named "enemyNameBox" + enemyNumber, and I'm not sure how to do so.
What I did was create "archVariables" to store the values from whatever enemy tab is selected, then use the appropriate archVariable in the program's calculations. IE: archEnemyName. The idea is that whatever tab the user is currently selected on (determined via SelectedIndex) the TextBox from that page will be used for the program's output.
Here are the two things I've tried after researching the matter:
// Attempt 1
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
archEnemyNameBox = ((TextBox)Controls["enemyNameBox" + i]).Text;
}
}
This code simply throws a NullReferenceException when I press the button. So after researching more I tried this:
// Attempt 2
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)));
archEnemyNameBox = tb2.Text;
}
}
This time I got an Error: Cannot convert type 'System.Windows.Forms.Control[]' to 'System.Windows.Forms.TextBox'
I feel like the second method I have here is probably closer to the correct way to do this, but apparently I'm still not getting it right. I've learned a lot by searching the information on stackoverflow and msdn.microsoft but nothing has gotten me past this problem.
Any help would be appreciated.
basically the problem with your second attemp is that enemyTab.Controls.Find("enemyNameBox" + i, true) returns an array of Controls Control[] and you're trying to convert that to a Control here is the problem, you should get the first control in that array and then convert it to a Control so it should be like this:
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)[0]));
archEnemyNameBox = tb2.Text;
}
}
but it is not the BestWay to do so it seems that everytime a user adds a new tabPage it will have the same Controls right? so why not create an userControl with any Control you have on your TabPage? so when you press the user press to add a new tab your code should be like so:
private void CreateNewEnemyTab()
{
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
EnemyTabUserControl enemyTab = new EnemyTabUserControl(enemyNumber);
here the EnemyTabUserControl should have all the components you need;
newTabPage.Controls.Add(enemyTab);
tabControl1.TabPages.Add(newTabPage);
}
and the code to bring the TextBox from the current tab could be as follow (you are going to need to reference LINQ)
using System.Linq;
//First Lets create this property, it should return the selected EnemyTabUserControl inside the tabControl
public EnemyTabUserControl CurrentTab {
get {
return tabControl1.SelectedTab.Controls.OfType<EnemyTabUserControl>().First();
}
}
// then if we make the textbox you want to reference from outside the code we can do this
CurrentTab.NameOfTheTextBox;
Patrick has solved your fundamental problem, but I don't think you need the loop in there at all. Here I've broken the steps out so you can see what needs to happen a little better:
private void defendCalcButton_Click(object sender, EventArgs e)
{
Control[] matches = this.Controls.Find("enemyNameBox" + tabControl1.SelectedIndex.ToString(), true);
if (matches.Length > 0 && matches[0] is TextBox)
{
TextBox tb = (TextBox)matches[0];
archEnemyNameBox = tb.Text;
}
}
How can I order a list of labels by their location on screen?
My labels move around the screen but only in X axis, this is the code that I have but I notice it isn't working.
labels.OrderBy(x => x.Location.X);
Thank you in advance!
Edit: This is how I'm testing if it works or not...
private void actualizarPosicoes() {
labels.OrderBy(x => x.Location.X);
MessageBox.Show(labels.First.Value.Text.ToString());
}
But I want to use it when a label is removed from the screen but first I have to make the OrderBy working.
private void reorder(Label lb) {
labels.OrderBy(x => x.Location.X);
var it = labels.GetEnumerator();
var node = it.Current;
while (it.MoveNext())
{
var nextNode = it.MoveNext();
if (nextNode != null)
{
if (nextNode.Equals(lb))
{
nextNode = it.MoveNext();
it.Current.Location = new Point(node.Right, 0);
}
}
node = it.Current;
}
}
I have a global linkedlist called labels:
private LinkedList<Label> labels;
I suggest you to use FlowLayoutPanel as container to your labels. Removing any label will automatically re-arrange other labels.
Here is sample - assume you have labelsFlowLayoutPanel to host labels and addLabelButton to add labels:
private void addLabelButton_Click(object sender, EventArgs e)
{
Label label = new Label();
label.Text = DateTime.Now.ToLongTimeString();
label.DoubleClick += label_DoubleClick;
labelsFlowLayoutPanel.Controls.Add(label);
}
void label_DoubleClick(object sender, EventArgs e)
{
Label label = (Label)sender;
labelsFlowLayoutPanel.Controls.Remove(label);
label.DoubleClick -= label_DoubleClick;
}
Each time you click addLabelButton new label is created and inserted to the right of other labels. Double-clicking on any label will remove it and re-arrange other labels.
Further info - Windows Forms Controls Lesson 5: How to use the FlowLayout Panel
Without offering any suggestions for a better approach, and to answer the question directly, the problem with you code is as follows:
The OrderBy method with not modify the instance is is called on, it will instead return a new IOrderedEnumerable<TSource> where TSource is the type of object, for example Label. This means that your code should look like this:
labels = labels.OrderBy(x => x.Location.X);
Depending on what type of object labels is, you may need to convert it to a List like so:
labels = labels.OrderBy(x => x.Location.X).ToList();
EDIT: I just realized you are using LinkedList, I would recommend changing this to private List<Label> labels;
I finally did it!
There is the answer if anyone else need it...
private void reorder() {
var it2 = labels.OrderBy(x => x.Location.X);
Label orderLb = it2.ElementAt(0);
for (int i = 1; i < labels.Count; i++)
{
it2.ElementAt(i).Location = new Point(orderLb.Right, 0);
orderLb = it2.ElementAt(i);
}
Thank you all for the help, really appreciate it!
I have a System.Windows.Forms.Panel with some content.
I am trying to programmatically scroll the panel (vertically) either up or down.
I have tried setting the AutoScrollPosition property to a new Point on the panel but that doesn't seem to do it.
I have the AutoScroll property set to true.
I even tried to set the VerticalScroll.Value twice as suggested here, but that doesn't seem to work either.
This is what I am currently doing:
//I have tried passing both positive and negative values.
panel.AutoScrollPosition = new Point(5, 10);
The X and Y values on AutoScrollPosition remain 0 and 0.
Any help or direction on this would be greatly appreciated it.
Thanks in advance,
Marwan
Here is a solution. I guess you can scroll your Panel by arbitrary position using Win32 however there is a simple trick to help you achieve your requirement here:
public void ScrollToBottom(Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
//use the code
ScrollToBottom(yourPanel);
Or use extension method for convenience:
public static class PanelExtension {
public static void ScrollToBottom(this Panel p){
using (Control c = new Control() { Parent = p, Dock = DockStyle.Bottom })
{
p.ScrollControlIntoView(c);
c.Parent = null;
}
}
}
//Use the code
yourPanel.ScrollToBottom();
UPDATE
If you want to set the exact position, modifying the code above a little can help:
//This can help you control the scrollbar with scrolling up and down.
//The position is a little special.
//Position for scrolling up should be negative.
//Position for scrolling down should be positive
public static class PanelExtension {
public static void ScrollDown(this Panel p, int pos)
{
//pos passed in should be positive
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + pos })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p, int pos)
{
//pos passed in should be negative
using (Control c = new Control() { Parent = p, Height = 1, Top = pos})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
int i = 0;
private void buttonUp_Click(object sender, EventArgs e)
{
if (i >= 0) i = -1;
yourPanel.ScrollUp(i--);
}
private void buttonDown_Click(object sender, EventArgs e)
{
if (i < 0) i = 0;
yourPanel.ScrollDown(i++);
}
Another solution you may want to use is using Panel.VerticalScroll.Value. However I think you need more research to make it work as you expect. Because I can see once changing the Value, the scrollbar position and control position don't sync well. Notice that Panel.VerticalScroll.Value should be between Panel.VerticalScroll.Minimum and Panel.VerticalScroll.Maximum.
This surprisingly works! NOTE THE MINUS SIGN in the code. There is strange behavior in setting scroll position. If you set the position to exact value (50), it goes negative when you read it next time (-50). So you have to invert it before setting new scroll value.
Scroll down:
private void ButtonScrollDown_OnClick(object sender, EventArgs e)
{
Point current = yourScrollPanel.AutoScrollPosition;
Point scrolled = new Point(current.X, -current.Y + 10);
yourScrollPanel.AutoScrollPosition = scrolled;
}
Scroll up similarly, (-current.Y - 10)
If you have a class that derives from Panel, then call these two protected methods to scroll the panel:
// The bottom is off screen; scroll down. These coordinates must be negative or zero.
SetDisplayRectLocation(0, AutoScrollPosition.Y - item.BoundingRect.Bottom + ClientRectangle.Bottom);
AdjustFormScrollbars(true);
In my example, item.BoundingRect.Bottom is the Y coordinate of the bottom of a thumbnail, and I need to scroll the panel down so that the whole thumbnail is visible.
#King King's solution of creating a temporary Control just so that scrolling could be done seemed "heavy" to me. And #Hans Passant's suggestion of setting AutoScrollMinSize and AutoScrollPosition didn't work for me.
Leave AutoScroll to its default value of 'true'.
Try this:-
panel.ScrollControlIntoView(childcontrol);
This should work. childcontrol is the particular control that you want to show in your display area.
Setting the value of the HorizontalScroll property and then using the method ScrollControlIntoView works for me:
lpanel.HorizontalScroll.Value = 100;
lpanel.ScrollControlIntoView(lpanel);
Use #King King Answered Code and if you want to hide horizontal and vertical scroll bar, just apply the below code in the constructor or initialization.
yourPanel.AutoScroll = false;
yourPanel.HorizontalScroll.Maximum = 0;
yourPanel.HorizontalScroll.Visible = false;
yourPanel.VerticalScroll.Maximum = 0;
yourPanel.VerticalScroll.Visible = false;
yourPanel.AutoScroll = true;
I had an issue where I couldnt get my panel to scroll back to top . I tried many things to try and get the panel to scroll back to the top after populating it with many controls.
Nomatter what I did it always put the VScroll bar to the bottom.
After exhaustive testing I found it was because my controls had the TabStop property set to true (default on user controls) was causing the issue.
Setting TabStop to false fixed it.
Create an control that sits slightly outside the visible area (so -1 at the top and clientsize+1 ) and then call ScrollControlIntoView:
public static class PanelExtension {
public static void ScrollDown(this Panel p)
{
using (Control c = new Control() { Parent = p, Height = 1, Top = p.ClientSize.Height + 1 })
{
p.ScrollControlIntoView(c);
}
}
public static void ScrollUp(this Panel p )
{
using (Control c = new Control() { Parent = p, Height = 1, Top = -1})
{
p.ScrollControlIntoView(c);
}
}
}
//use the code, suppose you have 2 buttons, up and down to control the scrollbar instead of clicking directly on the scrollbar arrows.
private void buttonUp_Click(object sender, EventArgs e)
{
yourPanel.ScrollUp();
}
private void buttonDown_Click(object sender, EventArgs e)
{
yourPanel.ScrollDown();
}
with yourpanel.SetAutoScrollMargin(1, 1); you can set very fine scrolling steps and then take a timer to call the srolling when buttons are down