I have a little application made in Java and I want to build it in C# too. But I encounter a panel size limitation.
I add some custom Panel as records in a holder panel on a Form, by reading a file and for each line in that file I instantiate a new object in my holder Panel.
In Java you can add as many objects you want in a JPanel, as it resize and view all the objects inside it, using a JScrollPane. Anyway, I have a file with 1554 records inside and my Java application it will show all the objects, but in C# it shows me only 738 records, because of size limitation.
I have tried to add a panel "b" to that holder panel, and to add all the records (custom panel) in that panel b, and setting its height as Int32.MaxValue.
I have set the BorderStyle to FixedSingle to be able to view the size of panel b. It allows me to scroll more than Int16.MaxValue, but my objects are shown only till that Int16.MaxValue value.
The only solution is by paging all records?
It's not difficult to setup a ListView as the Control you showed.
You just need to paint some parts of its Items yourself.
Set:
1. ListView.OwnerDraw= true
2. ListView.View= View.Details
3. Add one Column, the size of the ListView minus the size of the ScrollBar (SystemInformation.VerticalScrollBarWidth)
4. ListView.HeaderStyle= none if you don't want to show the header.
5. Subscribe to the ListView.DrawSubItem event
6. Add an ImageList, set its ImageSize.Height to the height of the Items of your ListView and select it as the ListView.StateImageList (so it won't be necessary to create a Custom Control to define the Items' height).
Here I've added an utility that selects the Text formatting style based on the current alignment of the Items' Text. It won't be necessary if you align the Text to the left only.
In case you have a very long list of Items to add to the ListView, a VirtualMode is available.
It's not that different from the one you've shown, right?.
Color lvPanelsItemCurrentBackColor = Color.FromArgb(58, 188, 58);
Color lvPanelsItemSelectedBackColor = Color.FromArgb(48, 48, 48);
Color lvPanelsItemBackColor = Color.FromArgb(28,28,28);
Color lvPanelsItemForeColor = Color.White;
private void lvPanels_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
var lView = sender as ListView;
TextFormatFlags flags = GetTextAlignment(lView, e.ColumnIndex);
Color itemBackColor = lvPanelsItemBackColor;
Rectangle itemRect = e.Bounds;
itemRect.Inflate(-2, -2);
if (e.Item.Selected || e.Item.Focused) {
itemBackColor = e.Item.Focused ? lvPanelsItemCurrentBackColor : lvPanelsItemSelectedBackColor;
}
using (SolidBrush bkgrBrush = new SolidBrush(itemBackColor)) {
e.Graphics.FillRectangle(bkgrBrush, itemRect);
}
TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font, e.Bounds, lvPanelsItemForeColor, flags);
}
private TextFormatFlags GetTextAlignment(ListView lstView, int colIndex)
{
TextFormatFlags flags = (lstView.View == View.Tile)
? (colIndex == 0) ? TextFormatFlags.Default : TextFormatFlags.Bottom
: TextFormatFlags.VerticalCenter;
flags |= TextFormatFlags.LeftAndRightPadding | TextFormatFlags.NoPrefix;
switch (lstView.Columns[colIndex].TextAlign)
{
case HorizontalAlignment.Left:
flags |= TextFormatFlags.Left;
break;
case HorizontalAlignment.Right:
flags |= TextFormatFlags.Right;
break;
case HorizontalAlignment.Center:
flags |= TextFormatFlags.HorizontalCenter;
break;
}
return flags;
}
Related
I have a little problem and can't find a solution.
I would like to put a text at the end of a ListBox item and I have no idea how...
TagLib.File f = TagLib.File.Create(paths[i]);
listBox1.Items.Add("0" + i + ". " + f.Tag.Title +"\n" + string.Join(", ", f.Tag.Performers) + " - " + "\r" + f.Tag.Album + " " + f.Properties.Duration.TotalMinutes.ToString());
I've already tried it but unfortunately it doesn't work that way. Maybe someone knows how you can always put the text at the end, with a code?
I want the text of all items to match each other
And I can't find a solution how to put the text at the end and how the text can match each other
To add multiline text to a ListBox Control, you need to measure and draw the text yourself.
Set the ListBox.DrawMode to DrawMode.OwnerDrawVariable, then override OnMeasureItem and OnDrawItem.
→ OnMeasureItem is called before an Item is drawn, to allow to define the Item Size, setting the MeasureItemEventArgs e.ItemWidth and e.ItemHeight properties (you have to verify that the ListBox contains Items before trying to measure one).
→ When OnDrawItem is called, the e.Bounds property of its DrawItemEventArgs will be set to the measure specified in OnMeasureItem.
To measure the Text, you can use both the TextRenderer class MeasureText() method or Graphics.MeasureString. The former is preferred, since we're going to use the TextRenderer class to draw the Items' text: TextRenderer.DrawText is more predictable than Graphics.DrawString() in this context and it renders the text naturally (as the ListBox - or ListView - does).
The TextRenderer's TextFormatFlags are used to fine-tune the rendering behavior. I've added TextFormatFlags.ExpandTabs to the flags, so you can also add Tabs ("\t") to the Text when needed. See the visual example.
"\n" can be used to generate line feeds.
In the sample code, I'm adding 8 pixels to the measured height of the Items, since a line separator is also drawn to better define the limits of an Item (otherwise, when a Item spans more than one line, it may be difficult to understand where its text starts and ends).
▶ Important: the maximum Item.Height is 255 pixels. Beyond this measure, the Item's text may not be rendered or be rendered partially (but it usually just disappears). There's a Min/Max check on the item height in the sample code.
This is how it works:
I suggest to use a class object, if you haven't, to store your Items
and describe them. Then use a List<class> as the
ListBox.DataSource. This way, you can better define how each part
should be rendered. Some parts may use a Bold Font or a different
Color.
using System;
using System.Drawing;
using System.Windows.Forms;
public class ListBoxMultiline : ListBox
{
TextFormatFlags flags = TextFormatFlags.WordBreak |
TextFormatFlags.PreserveGraphicsClipping |
TextFormatFlags.LeftAndRightPadding |
TextFormatFlags.ExpandTabs |
TextFormatFlags.VerticalCenter;
public ListBoxMultiline() { this.DrawMode = DrawMode.OwnerDrawVariable; }
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (Items.Count == 0) return;
if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) {
using (var selectionBrush = new SolidBrush(Color.Orange)) {
e.Graphics.FillRectangle(selectionBrush, e.Bounds);
}
}
else {
e.DrawBackground();
}
TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, ForeColor, flags);
if (e.Index != Items.Count - 1) {
Point lineOffsetStart = new Point(e.Bounds.X, e.Bounds.Bottom - 1);
Point lineOffsetEnd = new Point(e.Bounds.Right, e.Bounds.Bottom - 1);
e.Graphics.DrawLine(Pens.LightGray, lineOffsetStart, lineOffsetEnd);
}
base.OnDrawItem(e);
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
if (Items.Count == 0) return;
var size = GetItemSize(e.Graphics, GetItemText(Items[e.Index]));
e.ItemWidth = size.Width;
e.ItemHeight = size.Height;
base.OnMeasureItem(e);
}
private Size GetItemSize(Graphics g, string itemText)
{
var size = TextRenderer.MeasureText(g, itemText, Font, ClientSize, flags);
size.Height = Math.Max(Math.Min(size.Height, 247), Font.Height + 8) + 8;
return size;
}
}
I've got a WinForms comboBox with the datasouce set to a list of a custom class. I'm displaying these items as colors (based on a property in the class) and would like to only display the color (i.e. no text). I'm displaying the items as colors in the dropdown through the DrawItem event, but this doesn't work for the comboBox itself (the part other than the dropdown). I've tried changing the ForeGround Color to Transparent, but that didn't work either. What I'd really like is a comboBox.DisplayMember = "None"; or something similar.
What is the best way to accomplish this?
Edit: So after a bit of fiddling around, I've found one solution: adding a "None" property to the class like this:
public string None
{
get
{
return "";
}
}
then I can just do the comboBox.DisplayMember = "None"; like I mentioned before. But I think the question still stands: is there a better way?
You can make a ComboBox control as a color picker to display and select colors by using DrawItem event and also there is a property called DrawMode for the ComboBox control which determines whether the Operating System or the code will handle the drawing of the items in the list. This property must be set to OwnerDrawFixed using the Properties window in order for the DrawItem event implementation to be called.
private void ColorComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
Rectangle rect = e.Bounds;
if (e.Index >= 0)
{
Color c = Color.FromName(n);
Brush b = new SolidBrush(c);
g.DrawString(n, f, Brushes.Black, rect.X, rect.Top);
g.FillRectangle(b, rect.X, rect.Y + 5, rect.Width -10, rect.Height - 10);
}
}
You can read more about CodeProject: Color Picker Combo Box
As my "pseudo-solution" seems to be the best solution here, I'll just copy it down here:
Since the items in my comboBox are of a custom class, I added another property to that class:
public string None
{
get
{
return "";
}
}
and I set the comboBox.DisplayMember = "None";. This achieves the result I was looking for
I'm not entirely sure this is possible using C# and Windows Forms, but here goes:
I want to split my window into two. On the left is a series of buttons arranged vertically.
On clicking each button, the stuff on the right side has to change. I want different controls displaying for different buttons clicks.
(Kind of like web browser tabs, just on the left pane instead of on top)
I'm guessing I have to use split container, but most of the split container tutorials I've seen have them using the same control on the right, just with different data displaying every time something is clicked.
I hope I'm being clear with what I need. Any help or pointers would be greatly appreciated.
You can get the job done with a TabControl, as explained on MSDN. It shows an example on how to put the tabs on the right side but switching "right" with "left" should provide the behavior you seek.
I tried it in a WinForms app and it seems to work fine, despite the ugly colors used in the example... The following takes ~30 secs to implement.
Quoting from the link:
To display right-aligned tabs
Add a TabControl to your form.
Set the Alignment property to Right. if you set Left it goes on the left no problem
Set the SizeMode property to Fixed, so that all tabs are the same width.
Set the ItemSize property to the preferred fixed size for the tabs. Keep in mind that the ItemSize property behaves as though the tabs were on top, although they are right-aligned. As a result, in order to make the tabs wider, you must change the Height property, and in order to make them taller, you must change the Width property.
Set the DrawMode property to OwnerDrawFixed.
Define a handler for the DrawItem event of TabControl that renders the text from left to right.
And this is the code (converted to C# since MSDN only shows VB):
private void tabControl1_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
Brush _TextBrush = default(Brush);
// Get the item from the collection.
TabPage _TabPage = tabControl1.TabPages[e.Index];
// Get the real bounds for the tab rectangle.
Rectangle _TabBounds = tabControl1.GetTabRect(e.Index);
if ((e.State == DrawItemState.Selected))
{
// Draw a different background color, and don't paint a focus rectangle.
_TextBrush = new SolidBrush(Color.Red);
g.FillRectangle(Brushes.Gray, e.Bounds);
}
else
{
_TextBrush = new System.Drawing.SolidBrush(e.ForeColor);
e.DrawBackground();
}
// Use our own font.
Font _TabFont = new Font("Arial", 10, FontStyle.Bold, GraphicsUnit.Pixel);
// Draw string. Center the text.
StringFormat _StringFlags = new StringFormat();
_StringFlags.Alignment = StringAlignment.Center;
_StringFlags.LineAlignment = StringAlignment.Center;
g.DrawString(_TabPage.Text, _TabFont, _TextBrush, _TabBounds, new StringFormat(_StringFlags));
}
Just put your content in the TabControl and you're (sorta) good to go.
It is possible and one way is to create new user controls for each of the right-hand interfaces. When you click a button it can load a new instance of one of these controls into the right-hand panel:
if (splitContainer.Panel2.Controls.Count > 0)
{
splitContainer.Panel2.Controls[0].Dispose(); // Edit
splitContainer.Panel2.Controls.Clear();
}
splitContainer.Panel2.Controls.Add(new RightHandControl());
This seems to work when I have tried in the past.
EDIT: As has been pointed out in the comments, this example does not properly dispose of the user controls when cleared! To solve this, call Dispose() prior to clearing the list.
The exact layout and the controls you need will be up to you.This is meant as an simplistic starting point to make you see the options you have(here of course 1 option is provided):
1) add splitcontainer to the form.
2) add flowLayoutPanel to left panel of splitcontainer and dock in parent container
3) since you asked for vertical layout set FlowDirection on the
flowLayoutPanel to topdown
4) in my example i used 6 buttons and named them(instead could be
their text) btncontrol1,2,3...so on, do the same.
5) set all buttons click handler to the same(in this case
buttons_Click)
6) copy this code and paste
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttons_Click(object sender, EventArgs e)
{
Button b = sender as Button;
switch (b.Name)
{
//of course after each ClearPanel what i do is only for
//demonstration.
case "btnControl1":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new ListBox());
splitContainer1.Panel2.ResumeLayout();
break;
case "btnControl2":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new RadioButton());
splitContainer1.Panel2.ResumeLayout();
break;
case "btnControl3":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new Button());
splitContainer1.Panel2.ResumeLayout();
break;
case "btnControl4":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new DateTimePicker());
splitContainer1.Panel2.ResumeLayout();
break;
case "btnControl5":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new DataGridView());
splitContainer1.Panel2.ResumeLayout();
break;
case "btnControl6":
splitContainer1.Panel2.SuspendLayout();
ClearPanel();
splitContainer1.Panel2.Controls.Add(new TextBox());
splitContainer1.Panel2.ResumeLayout();
break;
default:
break;
}
}
private void ClearPanel()
{
if (splitContainer1.Panel2.HasChildren)
{
foreach (Control c in splitContainer1.Panel2.Controls)
{
c.Dispose();
}
splitContainer1.Panel2.Controls.Clear();
}
}
}
Hope it help you.
I have a TabControl with two tab pages.
How can I make the tab pages fit into the width of the TabControl like shown in the below screenshot.
I tried with the following line of code but it does not work either.
tabControl1.SizeMode = TabSizeMode.FillToRight;
First, set your tabControl1 size mode:
tabControl1.SizeMode = TabSizeMode.Fixed;
Then you have to recalculate width of the tab page header:
tabControl1.ItemSize = new Size(tabControl1.Width / tabControl1.TabCount, 0);
Pay attention: 1. value 0 means that height will be default. 2. Recalculate item size after you had added tab page to tab control. Consider what happens when you resize the control.
Contributing based on Jarek's answer. If you want tabs widths to change dynamically as you resize the TabControl as well, you can achieve this by implementing this on the TabControl Resize Event:
private bool doNotExecuteResizeEventAgain = true;
private void Tab_Control_Resize(object sender, EventArgs e)
{
if(doNotExecuteResizeEventAgain)
{
int tabWidth = ((int)(tab_Control.Width/tab_Control.TabPages.Count)) - 1;
doNotExecuteResizeEventAgain = false;
tab_Control.ItemSize = new Size(tabWidth, tab_Details.ItemSize.Height);
doNotExecuteResizeEventAgain = true;
}
}
The use of the variable doNotExecuteResizeEventAgain is because the ItemSize calls again the event Resize, so in order to stop it from cycling, I added that flag.
Use SizeMode on the TabControl: http://msdn.microsoft.com/en-us/library/system.windows.forms.tabcontrol.sizemode.aspx
this is illegal way to solve this kind of issue
Increase the padding (X,Y)
X 100 // represent some allowed figures
Y 3 // represent some allowed figure.
I know I can change the ForeColor of the ComboBox like this:
comboBox1.ForeColor = Color.Red;
But that makes all the items that color. When you drop down the ComboBox every single item is then red.
I want to individually color items so that the first item is always black, the second always red, the third always blue, et cetera. Is this possible?
Also, I don't think I can create a UserControl for this because the ComboBox I am using is the one for Toolstrips.
You can use DrawItem event.
This event is used by an owner-drawn ComboBox. You can use this event
to perform the tasks needed to draw items in the ComboBox. If you have
a variable sized item (when the DrawMode property set to
DrawMode.OwnerDrawVariable), before drawing an item, the MeasureItem
event is raised. You can create an event handler for the MeasureItem
event to specify the size for the item that you are going to draw in
your event handler for the DrawItem event.
MSDN Example:
// You must handle the DrawItem event for owner-drawn combo boxes.
// This event handler changes the color, size and font of an
// item based on its position in the array.
protected void ComboBox1_DrawItem(object sender,
System.Windows.Forms.DrawItemEventArgs e)
{
float size = 0;
System.Drawing.Font myFont;
FontFamily family = null;
System.Drawing.Color animalColor = new System.Drawing.Color();
switch(e.Index)
{
case 0:
size = 30;
animalColor = System.Drawing.Color.Gray;
family = FontFamily.GenericSansSerif;
break;
case 1:
size = 10;
animalColor = System.Drawing.Color.LawnGreen;
family = FontFamily.GenericMonospace;
break;
case 2:
size = 15;
animalColor = System.Drawing.Color.Tan;
family = FontFamily.GenericSansSerif;
break;
}
// Draw the background of the item.
e.DrawBackground();
// Create a square filled with the animals color. Vary the size
// of the rectangle based on the length of the animals name.
Rectangle rectangle = new Rectangle(2, e.Bounds.Top+2,
e.Bounds.Height, e.Bounds.Height-4);
e.Graphics.FillRectangle(new SolidBrush(animalColor), rectangle);
// Draw each string in the array, using a different size, color,
// and font for each item.
myFont = new Font(family, size, FontStyle.Bold);
e.Graphics.DrawString(animals[e.Index], myFont, System.Drawing.Brushes.Black, new RectangleF(e.Bounds.X+rectangle.Width, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
// Draw the focus rectangle if the mouse hovers over an item.
e.DrawFocusRectangle();
}
EDIT :
Just found a similar thread.
For a ToolStripComboBox derive from ToolStripControlHost.
//Declare a class that inherits from ToolStripControlHost.
public class ToolStripCustomCombo : ToolStripControlHost
{
// Call the base constructor passing in a MonthCalendar instance.
public ToolStripCustomCombo() : base(new ComboBox()) { }
public ComboBox ComboBox
{
get
{
return Control as ComboBox;
}
}
}
Then say you have a toolstrip named m_tsMain. Here's how to add the new control.
ToolStripCustomCombo customCombo = new ToolStripCustomCombo();
ComboBox c = customCombo.ComboBox;
c.Items.Add("Hello World!!!");
c.Items.Add("Goodbye cruel world!!!");
m_tsMain.Items.Add(customCombo);
And you should be able to add an event handler to c for DrawItem.