When I add item(s) to the ListView in any other than LargeIcon view mode, the ListView stops showing the images from LargeImageList when it is switched back to LargeIcon. This situation lasts until new item is added to the ListView in the LargeIcon mode.
So the following sequence illustrates the problem:
create ListView, add column, set View to Details
create ImageList, set ImageSize, assign it to ListView.LargeImageList
create new ListViewItem, set its ImageKey
create new image, add it to the ImageList with given key
add the ListViewItem to the ListView
switch the ListView mode to LargeIcon
no images are shown
repeat steps #3 - #6, now in the LargeIcon mode
all images are shown as expected
What is the point I am still missing?
I have tried following:
Invalidate the ListView
Re-assign the LargeImageList before/after adding the item (even through null)
The test code for those who like it more than words:
public partial class Form1 : Form
{
int counter = 0;
ImageList iList = new ImageList();
private string GetNewKey()
{
return counter++.ToString();
}
private Image GetNewImage(Size size)
{
var bmp = new Bitmap(size.Width, size.Height);
using (var gra = Graphics.FromImage(bmp))
{
var rnd = new Random();
var lines = rnd.Next(1000);
for (int l = 0; l < lines; ++l)
{
var pen = new Pen(Color.FromArgb(rnd.Next(256), rnd.Next(256), rnd.Next(256)));
var p1 = new Point(rnd.Next(size.Width), rnd.Next(size.Height));
var p2 = new Point(rnd.Next(size.Width), rnd.Next(size.Height));
gra.DrawLine(pen, p1, p2);
}
}
return bmp;
}
public Form1()
{
InitializeComponent();
iList.ImageSize = new Size(100, 100);
listView.LargeImageList = iList;
listView.Columns.Add("name");
}
private void buttonAdd_Click(object sender, EventArgs e)
{
var key = GetNewKey();
var lvi = new ListViewItem()
{
Name = key,
Text = "blabla",
ImageKey = key,
};
iList.Images.Add(key, GetNewImage(new Size(100, 100)));
listView.Items.Add(lvi);
}
private void buttonClear_Click(object sender, EventArgs e)
{
listView.Items.Clear();
}
private void buttonLarge_Click(object sender, EventArgs e)
{
listView.View = View.LargeIcon;
}
private void buttonDetails_Click(object sender, EventArgs e)
{
listView.View = View.Details;
}
}
EDIT:
For anyone who would suffer the same problem. After some experiments, there is at least stupid poor man's workaround:
Modify the ImageList, the ListView somehow detects its change and reloads the images for LargeIcon mode. The questions are how it detects the change and why does it ignore the ImageList after mode change...
private void FixIt()
{
// Trigger a reload of the ListView.LargeImageList
if (listView.View == View.LargeIcon)
{
var key = "Dummy image to be deleted right after its insertion...";
iList.Images.Add(key, new Bitmap(1, 1));
iList.Images.RemoveByKey(key);
}
}
EDIT #2:
I have also discovered some other funny features the ListView and associated components have. You might want to check them in answers of question 4097912 and question 23059678
To solve your problem
You could avoid this by useing ImageIndex instead of ImageKey to connect your ListView with the ImageList. So in your buttonAdd_Click event use:
var lvi = new ListViewItem()
{
Name = key,
Text = "blabla",
//ImageKey = key,
//Use ImageIndex and don't set both
ImageIndex= Convert.ToInt32(key) //you could just use count++
};
The reason behind this problem:
The reason behind this is not clear to me, but I assume this may be a bug that when changing from Details to LargeIcon, it only checks ImageIndex in default and if you set ImageKey the ImageIndex will be set to -1. Or may be this is by design, I don't know (see ImageKey section below), since you don't have a SmallImageList, so when changing to LargeIcon view, ImageIndex is null or -1 and the ImageKey is ignored.
About ListViewItem.ImageIndex Property
The ImageKey and ImageIndex properties are mutually exclusive, meaning if one is set, the other is ignored. Furthermore, if you set the ImageKey property, the ImageIndex property is automatically set to -1. Alternatively, if you set the ImageIndex property, the ImageKey is automatically set to an empty string ("").
About ListViewItem.ImageKey Property
If you are using multiple image lists, for small and large icon view, with a ListView control, you should place small and large versions of the image at the same index location in their respective image lists. When switching between views, the index location of the image in one list is used to locate the image in the other list, regardless of the key value specified.
And this can somehow be verified:
using your existing code (use ImageKey)
set ImageIndex for any Item in your listView within the buttonLarge_Click event handler will show you that item's image.
set ImageKey for any Item within the buttonLarge_Click event handler will not show that itme's image.
e.g.:
private void buttonLarge_Click(object sender, EventArgs e)
{
listView.View = View.LargeIcon;
//Set ImageIndex of Item 0 you could see its Icon.
listView.Items[0].ImageIndex= 0 ;
//set ImageKey will change nothing
//listView.Items[0].ImageKey= "0" ;
}
Related
I want to shown a context menu where the menu items are images that are laid out in a grid. However, when I set LayoutStyle to ToolStripLayoutStyle.Table in the ToolStripDropDown of a menu, it will only give a grid layout of the menu items if a new ToolStripDropDown object is created.
My problem is that I can create and assign a new ToolStripDropDown for a sub-menu, but not for ContextMenuStrip, because it is the ToolStripDropDown.
The following code demonstrates the problem. It will display a context menu that contains colours swatch images and also has two sub-menus with the same images. All three menus have the LayoutStyle property set to ToolStripLayoutStyle.Table, but only one will actually show as a grid.
private void FillDropDown(ToolStripDropDown drop_down)
{
// Set the drop down to a 2 column table layout
drop_down.LayoutStyle = ToolStripLayoutStyle.Table;
TableLayoutSettings table_layout_settings = (TableLayoutSettings)drop_down.LayoutSettings;
table_layout_settings.ColumnCount = 2;
// Fill the menu with some colour swatches
Color[] colours = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color colour in colours) {
ToolStripMenuItem item = new ToolStripMenuItem();
Bitmap swatch = new Bitmap(64, 64);
using (Graphics g = Graphics.FromImage(swatch))
using (SolidBrush b = new SolidBrush(colour)) {
g.FillRectangle(b, 0, 0, 64, 64);
}
item.Image = swatch;
item.DisplayStyle = ToolStripItemDisplayStyle.Image;
item.Margin = new Padding(2, 2, 2, 2);
drop_down.Items.Add(item);
}
}
private void ShowColorMenu(Point screen_location)
{
ContextMenuStrip context_menu = new ContextMenuStrip();
// The root context menu will not layout as a grid
FillDropDown(context_menu);
// This sub-menu will not layout as a grid
ToolStripMenuItem sub_menu = new ToolStripMenuItem("Sub-menu");
FillDropDown(sub_menu.DropDown);
context_menu.Items.Add(sub_menu);
// A sub-menu will layout as a grid if we create a new ToolStripDropDown for it
ToolStripMenuItem grid_sub_menu = new ToolStripMenuItem("Grid Sub-menu");
ToolStripDropDown new_drop_down = new ToolStripDropDown();
FillDropDown(new_drop_down);
grid_sub_menu.DropDown = new_drop_down;
context_menu.Items.Add(grid_sub_menu);
context_menu.Show(screen_location);
}
On my machine the result appears as follows:
I would like to have a grid of images in the root of the context menu. It would also be nice to understand why it is behaving like this. I have looked through the .NET reference source, but that didn't help on this occasion.
The ContextMenuStrip cannot show its ToolStripMenuItems in a Table layout, because of a limitation in Menu presentation/layout and you also cannot cast ContextMenuStrip to ToolStripDropDown (or the other way around; it's a wrapper around ToolStripDropDownMenu, it cannot be transformed into its indirect ancestor: it will retain its specific functionality (e.g., you can see both a TextBox and a ListBox as Control, but it doesn't mean that, now, setting the Text of a ListBox will actually show a text somewhere, just because the Text property belongs to the Control class).
But you can directly use and show a ToolStripDropDown the same way as a ContextMenuStrip. The ToolStripDropDown's LayoutSettings can be cast directly to TableLayoutSettings and a LayoutStyle of type ToolStripLayoutStyle.Table is fully supported.
In the example, a ToolStripDropDown object, containing ToolStripMenuItems arranged in a Table layout, is used as a ContextMenuStrip to select a colored Image to be applied to a PictureBox Control, while the Color name is shown in a Label control.
The dropdown menu is created when the Form is initialized, it's shown clicking the Mouse right Button inside the Form's ClientArea and disposed of when the Form closes:
Note1: here, I'm using a Lambda to subscribe to the ToolStripDropDown.ItemClicked event. It's however preferable, with this type of control, to use a method delegate instead.
Note2: the ToolStripDropDown is disposed of calling contextColorMenu.Dispose();. If the container Form is opened and closed frequently, it may be better to explicitly dispose of the ToolStripMenuItems Images.
using System.Drawing;
using System.Windows.Forms;
public partial class SomeForm : Form
{
private ToolStripDropDown contextColorMenu = null;
public SomeForm()
{
InitializeComponent();
contextColorMenu = new ToolStripDropDown();
contextColorMenu.ItemClicked += (o, a) => {
// Assign the selected Bitmap to a PitureBox.Image
picColor.Image = a.ClickedItem.Image;
// Show the Color description in a Label
lblColor.Text = ((Color)a.ClickedItem.Tag).ToString();
};
FillDropDown(contextColorMenu);
}
private void ShowColorMenu(Point location) => contextColorMenu.Show(location);
private void FillDropDown(ToolStripDropDown dropDown)
{
dropDown.LayoutStyle = ToolStripLayoutStyle.Table;
(dropDown.LayoutSettings as TableLayoutSettings).ColumnCount = 2;
(dropDown.LayoutSettings as TableLayoutSettings).GrowStyle = TableLayoutPanelGrowStyle.AddRows;
Color[] colors = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Purple };
foreach (Color color in colors) {
var swatch = new Bitmap(64, 64);
using (var g = Graphics.FromImage(swatch)) {
g.Clear(color);
}
var item = new ToolStripMenuItem() {
DisplayStyle = ToolStripItemDisplayStyle.Image,
Image = swatch,
Tag = color,
Margin = new Padding(2),
Padding = new Padding(2, 1, 2, 1) // Fine tune the Items' Cell border
};
dropDown.Items.Add(item);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Right) {
ShowColorMenu(MousePosition);
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
contextColorMenu?.Dispose();
}
}
I have a problem I've been looking into for a few days now, I just can't think of a logical way of doing what I want.
I have an app which has a task list. It starts of with 3 controls: a textbox, datetimepicker and a PictureBox which changes image on click. The user can then press an image which will add another row of controls below (It gets the properties of the dynamic controls from the controls already created):
https://www.dropbox.com/s/o2pub6orww24w25/tasklist.png (This is a screenshot to make it clearer)
Now what I want to do is save the values from each of the rows (A row being defined as: Textbox, Date, Status) into an SQLite DB.
For the first row it is easy, because that has a unique design name (and is a 'static' control).
However, the problem hits when I attempt to save values from the dynamic controls:
Problem a) I cannot reference the dynamic control because 'It does not Exist in the current Context'. -The function for creating the controls has a public access modifier so I thought that should do the trick? -It didn't. I've also tried:Panel1.pb.blah but it still didn't recognize the control?
Problem b) How can I tell my program that each row is a new set of data? In other words, how can I run a new insert command for each row? -I thought of doing this as a for-each-textbox loop, however would that not just pick up the first dynamic date everytime?
I've also thought of using the tag property and setting it to the counter variable, to group the controls in the row. (The counter being an integer which increments every time a new row is added.) However I cannot do that because the picture box uses the tag property as part of its function to change image on click (Changes multiple times).
Code:
Adding the Controls:
public void pictureBox1_Click(object sender, EventArgs e)
{
//TextBox Control
int tbh = tasktb.Location.Y + (counter*25);
int tbsh = tasktb.Size.Height;
int tbsw = tasktb.Size.Width;
TextBox tb = new TextBox();
tb.Location = new Point(9, tbh);
tb.Size = new System.Drawing.Size(tbsw, tbsh);
tb.Tag = counter.ToString();
//Date Time Control
int dth = duedatedb.Location.Y + (counter * 25);
int dtsh = duedatedb.Size.Height;
int dtsw = duedatedb.Size.Width;
DateTimePicker dtp = new DateTimePicker();
dtp.Location = new Point(300, dth);
dtp.Size = new Size(dtsw, dtsh);
dtp.Format = System.Windows.Forms.DateTimePickerFormat.Short;
//Picture Box Control
int stsh = status.Location.Y + (counter * 25);
int stssh = status.Size.Height;
int stssw = status.Size.Width;
PictureBox pb = new PictureBox();
pb.Location = new Point(429, stsh);
pb.Size = new Size(stssw, stssh);
pb.Image = Red;
pb.Click += new System.EventHandler(pb_Click);
panel1.Controls.Add(tb);
panel1.Controls.Add(dtp);
panel1.Controls.Add(pb);
++counter;
}
Trying to Reference the control: (For purposes of changing the image on click) [Found the Control.Find function from researching this in the MSDN Website]
public void pb_Click(object sender, EventArgs e)
{
PictureBox pb = (panel1.Controls.Find("pb",false));
if (pb.Image == Red) { pb.Image = Orange; status.Tag = "Orange"; }
else if (pb.Image == Orange) { pb.Image = green; status.Tag = "Green"; }
else if (pb.Image == green) { pb.Image = Red; status.Tag = "Red"; }
}
The essential problem here is Problem a, if you guys could see where I have gone wrong with that, I'd be able to go away and attempt to write some code to get around problem b.
(I have included Problem b in this for your suggestions on the best way to do this. -At the moment I have no clue!)
Thank you for any help received! It really is appreciated!
ControlCollection.Find looks for a control with the specified name, and you haven't set any. The variable names in your code aren't related. So, either:
pb.Name = "pb";
But that would mean you'd eventually have several items with the same name. So, seeing how you want to change the picture of the clicked PictureBox, just do this:
public void pb_Click(object sender, EventArgs e)
{
PictureBox pb = (PictureBox)sender;
if (pb.Image == Red) { pb.Image = Orange; status.Tag = "Orange"; }
else if (pb.Image == Orange) { pb.Image = green; status.Tag = "Green"; }
else if (pb.Image == green) { pb.Image = Red; status.Tag = "Red"; }
}
The sender argument always contains a reference to whichever control raised the event, in this case whichever picturebox was clicked!
Edit: As for your other question, I assume you'll need to do stuff to the controls later on, so I suggest you store a reference to all of them (or at least the ones you need), something like this:
// helper class
private class Entry
{
public TextBox TextBox { get; private set; }
public DateTimePicker DateTimePicker { get; private set; }
public PictureBox PictureBox { get; private set; }
public Entry( TextBox tb, DateTimePicker dtp, PictureBox pb )
{
this.TextBox = tb;
this.DateTimePicker = dtp;
this.PictureBox = pb;
}
}
// member field
private List<Entry> m_Entries = new List<Entry>();
// at the end of pictureBox1_Click
public void pictureBox1_Click(object sender, EventArgs e)
{
....
m_Entries.Add( new Entry( tb, dtp, pb ) );
}
Then you can use the items in that list to interact with your rows. You might also want to add an index, or a reference to whatever the original data structure is. Also, you might want to think about if you really should be creating the controls yourself like that or actually use some kind of table/grid control to host them!
Or perhaps just wrap up all those controls in a single UserControl, with logic included and all!
My question is basically, does implementing DrawItem for my ComboBox in WinForms, change my Text property, why and I can I stop it?
Because my OwnerDraw event works perfectly except the Text property "also" gets set to the same logic as all the items in Items[] (ie implemented in DrawItem event below)
For context, I show URL's in the list, but some are so long I basically chop them and put the text "..." at the end - to make it more readable. I have DataSource set so that it renders one property of my class "DisplayUrl" but uses another "Url" for the actual value. (MyUrl below)
At the end of some code, I explicitly set cmbUrl.Text = "THE FULL TEXT"
But somehow the DrawItem event is also effecting the "Text" property because even after running this code, once the DrawItem event is finished my Text property is set to the same as Item[0]. ie With the text chopped off - as in "THE FULL T..."
void cmbUrl_DrawItem(object sender, DrawItemEventArgs e)
{
var text = ((MyUrl)((ComboBox)sender).Items[e.Index]).DisplayUrl;
var brush = text.Contains("bla) ? Brushes.DarkGreen : Brushes.Black;
// Fill in the background
e.Graphics.FillRectangle(new SolidBrush(e.BackColor), e.Bounds);
if (e.Index < 0) return;
// Work out where every thing goes
int nX = e.Bounds.Left;
int nY = e.Bounds.Top;
const int nMarg = 2;
int nH = e.Bounds.Height - (2 * nMarg);
// Draw the Colour Gymph
var penFore = new Pen(e.ForeColor);
var rectGymph = new Rectangle(nX + nMarg, nY + nMarg, nH, nH);
e.Graphics.FillRectangle(brush, rectGymph);
e.Graphics.DrawRectangle(penFore, rectGymph);
var fullWidth = nX + nH + (2 * nMarg);
e.Graphics.DrawString(text, e.Font, brush, fullWidth, e.Bounds.Top);
}
I think you want to show your the full Text in your combobox and just want to show the short text in Items drop-down list, so the solution may be this:
private void cmbUrl_DropDown(object sender, EventArgs e){
cmbUrl.DisplayMember = "DisplayUrl";
}
private void cmbUrl_DropDownClosed(object sender, EventArgs e){
cmbUrl.DisplayMember = "Url";
}
Is it possible to animate ListView items in C# ? The purpose is that I have a ListView that is asynchronously modified, and I'd like to animate smoothly the items that have been modified, until the user clicks them. This way items blinking, or whatever would do the trick, are the items that changed which haven't been reviewed yet.
Thank you guys !
UPDATE : sorry, i forgot. Never used WPF before, and I think it's too late to switch to it now. I'm using winforms.
I had to do something similar to what you are trying to do but instead of animating the ListView, I used custom checkbox layout in the list view to look different. The designer code for the ListView looks like:
this.listView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
this.listView1.Location = new System.Drawing.Point(104, 90);
this.listView1.MultiSelect = false;
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(264, 105);
this.listView1.Sorting = System.Windows.Forms.SortOrder.Ascending;
this.listView1.TabIndex = 7;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseClick);
Then I declared a list that will hold the selection of the user.
private IList<ListViewItem> m_CheckedItems = new List<ListViewItem>();
Here is the initialise method for the ListView. You need to manipulate here for the initial look of your listview.
private void InitialiseListView(IList<string> data)
{
listView1.Items.Clear();
m_CheckedItems.Clear();
listView1.Columns.Clear();
listView1.Columns.Add("Col1");
listView1.Columns[0].Width = listView1.Width;
ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection(listView1);
ImageList images = new ImageList();
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Unchecked);
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Checked);
listView1.SmallImageList = images;
foreach (string str in data)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = 0;
item.Text = str;
collection.Add(item);
}
}
This event triggers when the user selects an option in the list view. The selection is recorded in the list I created above and the checked image is displayed so that it looks like the user has selected the item.
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && listView1.FocusedItem != null)
{
if (listView1.FocusedItem.ImageIndex == 1)
{
listView1.FocusedItem.ImageIndex = 0;
m_CheckedItems.Remove(listView1.FocusedItem);
}
else
{
listView1.FocusedItem.ImageIndex = 1;
m_CheckedItems.Add(listView1.FocusedItem);
}
}
}
You can probably fiddle with fonts and forecolor of these items ... Each item within a List View is of type ListViewItem so you can individually manipulate it.
Hope this gives you some direction :)
You could use a MyListView: ListView and override OnDrawSubItem. Have the e.Item.Tag to store the "Clicked" state and update the background according to its state.
public partial class ObjectListView : ListView {
(....)
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if(bool)e.Item.Tag)
(...) animate
}
You can do that easier in WPF.
WPF Basic
http://msdn.microsoft.com/en-us/library/ms754130.aspx
http://en.wikipedia.org/wiki/Windows_Presentation_Foundation
WPF Animation
http://msdn.microsoft.com/en-us/library/ms752312.aspx
ListView animation sample
(WPF) Animate ListView item move
WPF ListView animation by reorder of items?
A ToolStripComboBox is placed after a ToolStripButton and is folowed by another one, which is right-aligned. How do I best set up the ToolStripComboBox to always adjust its length to fill all the space available between the preceeding and the folowing ToolStripButtons?
In past I used to handle a parent resize event, calculate the new length to set based on neighboring elements coordinates and setting the new size. But now, as I am developing a new application, I wonder if there is no better way.
I use the following with great success:
private void toolStrip1_Layout(System.Object sender, System.Windows.Forms.LayoutEventArgs e)
{
int width = toolStrip1.DisplayRectangle.Width;
foreach (ToolStripItem tsi in toolStrip1.Items) {
if (!(tsi == toolStripComboBox1)) {
width -= tsi.Width;
width -= tsi.Margin.Horizontal;
}
}
toolStripComboBox1.Width = Math.Max(0, width - toolStripComboBox1.Margin.Horizontal);
}
The above code does not suffer from the disapearing control problem.
There's no automatic layout option for this. But you can easily do it by implementing the ToolStrip.Resize event. This worked well:
private void toolStrip1_Resize(object sender, EventArgs e) {
toolStripComboBox1.Width = toolStripComboBox2.Bounds.Left - toolStripButton1.Bounds.Right - 4;
}
protected override void OnLoad(EventArgs e) {
toolStrip1_Resize(this, e);
}
Be sure to set the TSCB's AutoResize property to False or it won't work.
ToolStrip ts = new ToolStrip();
ToolStripComboBox comboBox = new TooLStripComboBox();
comboBox.Dock = DockStyle.Fill;
ts.LayoutStyle = ToolStripLayoutStyle.Table;
((TableLayoutSettings)ts.LayoutSettings).ColumnCount = 1;
((TableLayoutSettings)ts.LayoutSettings).RowCount = 1;
((TableLayoutSettings)ts.LayoutSettings).SetColumnSpan(comboBox,1);
ts.Items.Add(comboBox);
Now the combobox will dock fill correctly. Set Column or Row span accordingly.