Adding multiple PictureBox to a C# Form - c#

I am trying to add several pictures (using PictureBox) of music notes to a Music Staff Form.
In a certain Mouse Event (MouseUp), a Music Note is created, and a music note should appear on the screen. However, only when the first note is created does the image appear on the screen. For every other note created after the first one, the image does not show up.
In the below method, the Music Note is created:
private void Key_MouseUp(object sender, MouseEventArgs e)
{
foreach (MusKey mk in panel1.Controls)
{
if (sender == mk) //true for the specific key pressed on the Music Keyboard
{
if (e.Button == MouseButtons.Left)
{
timer1.Enabled = false;
sp.Stop();
string bNoteShape = null;
// ticks -> milliseconds
if (count >= 16)
{
bNoteShape = "SemiBreve";
duration = 1024;
}
else if (count >= 8 && count <= 15)
{
bNoteShape = "DotMinim";
duration = 768;
}
else if (count >= 4 && count <= 7)
{
bNoteShape = "Crotchet";
duration = 384;
}
else if (count >= 2 && count <= 3)
{
bNoteShape = "Quaver";
duration = 128;
}
else
{
bNoteShape = "Semi-Quaver";
duration = 90; //63 is too short
}
MusicNote mn = new MusicNote(mk.getMusicNote(), duration, bNoteShape, mk.getNote());
Notes.Add(mn);
mn.picturebox1.Location = new Point(xLoc1, yLoc1);
panel2.Controls.Add(mn.picturebox1); //adding MusicNote component to MusicStaff (panel2) collection
}
}
}
}
When the Note is created, it is sent to the MusicNote constructor:
public MusicNote(int iNotepitch, int iDuration, String iBnoteShape, String iNote)
{
notepitch = iNotepitch;
noteduration = iDuration;
noteshape = iBnoteShape;
note = iNote;
picturebox1.ImageLocation = NoteImage_path + noteshape + ".bmp";
picturebox1.BackColor = Color.Transparent;
picturebox1.Location = new Point(xLoc, yLoc);
xLoc = xLoc + 15;
}
I have tried initializing the location (and incrementing xLoc) both through the constructor, and also in the form itself in method Key_MouseUp, but neither seems to make a difference.
The logic seems to be correct, as the picture of the first music note always loads, but I cannot understand why every other note after the first does not show up on my screen.
Any help will be appreciated, thanks!
Edit: perhaps maybe there is another alternative to PictureBox that I could use to store the music notes?

well it looks like your not initializing a new "picturebox1"
picturebox1 = new PictureBox(); //Add this
picturebox1.ImageLocation = NoteImage_path + noteshape + ".bmp";
edit:
Ok in general we need a few things to add a control to a form.
the new Control (look like you have that) 2
Adding it to the parent control (looks like you have that)
panel2.Controls.Add(mn.picturebox1); //adding MusicNote component to MusicStaff (panel2) collection
redraw the control --
this can happen automatically when you resize the form or you can call refresh(on the panel) after the new control is added to the panel.
panel2.Controls.Add(mn.picturebox1);
panel2.Refresh();

Related

Prevent ToolStripMenuItems from jumping to second screen

I have an application that is mostly operated through NotifyIcon's ContextMenuStrip
There are multiple levels of ToolStripMenuItems and the user can go through them.
The problem is, that when the user has two screen, the MenuItems jump to second screen when no space is available. like so:
How can I force them to stay on the same screen? I've tried to search through the web but couldn't find an appropriate answer.
Here is a sample piece of code i'm using to test this senario:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var resources = new ComponentResourceManager(typeof(Form1));
var notifyIcon1 = new NotifyIcon(components);
var contextMenuStrip1 = new ContextMenuStrip(components);
var level1ToolStripMenuItem = new ToolStripMenuItem("level 1 drop down");
var level2ToolStripMenuItem = new ToolStripMenuItem("level 2 drop down");
var level3ToolStripMenuItem = new ToolStripMenuItem("level 3 drop down");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
notifyIcon1.Icon = ((Icon)(resources.GetObject("notifyIcon1.Icon")));
notifyIcon1.Visible = true;
level2ToolStripMenuItem.DropDownItems.Add(level3ToolStripMenuItem);
level1ToolStripMenuItem.DropDownItems.Add(level2ToolStripMenuItem);
contextMenuStrip1.Items.Add(level1ToolStripMenuItem);
}
}
It is not easy, but you can write code in the DropDownOpening event to look at where the menu is at (its bounds), the current screen, and then set the DropDownDirection of the ToolStripMenuItem:
private void submenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
// Current bounds of the current monitor
Rectangle Bounds = menuItem.GetCurrentParent().Bounds;
Screen CurrentScreen = Screen.FromPoint(Bounds.Location);
// Look how big our children are:
int MaxWidth = 0;
foreach (ToolStripMenuItem subitem in menuItem.DropDownItems)
{
MaxWidth = Math.Max(subitem.Width, MaxWidth);
}
MaxWidth += 10; // Add a little wiggle room
int FarRight = Bounds.Right + MaxWidth;
int CurrentMonitorRight = CurrentScreen.Bounds.Right;
if (FarRight > CurrentMonitorRight)
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Also, make sure you have the DropDownOpening event hooked up (you would really need to add this to every menu item):
level1ToolStripMenuItem += submenu_DropDownOpening;
I have solved it this way:
For the ContextMenuStrip itself to open on a desired screen, I created a ContextMenuStripEx with the following methods:
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Rectangle dropDownBounds = new Rectangle(x, y, width, height);
dropDownBounds = ConstrainToBounds(Screen.FromPoint(dropDownBounds.Location).Bounds, dropDownBounds);
base.SetBoundsCore(dropDownBounds.X, dropDownBounds.Y, dropDownBounds.Width, dropDownBounds.Height, specified);
}
internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds)
{
if (!constrainingBounds.Contains(bounds))
{
bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height));
if (bounds.Right > constrainingBounds.Right)
{
bounds.X = constrainingBounds.Right - bounds.Width;
}
else if (bounds.Left < constrainingBounds.Left)
{
bounds.X = constrainingBounds.Left;
}
if (bounds.Bottom > constrainingBounds.Bottom)
{
bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height;
}
else if (bounds.Top < constrainingBounds.Top)
{
bounds.Y = constrainingBounds.Top;
}
}
return bounds;
}
(ConstrainToBounds method is taken from the base class ToolStripDropDown via Reflector)
for the nested MenuItems to open on the same screen as ContextMenuStrip, I created a ToolStripMenuItemEx (which derives from ToolStripMenuItem). In my case it looks like this:
private ToolStripDropDownDirection? originalToolStripDropDownDirection;
protected override void OnDropDownShow(EventArgs e)
{
base.OnDropDownShow(e);
if (!Screen.FromControl(this.Owner).Equals(Screen.FromPoint(this.DropDownLocation)))
{
if (!originalToolStripDropDownDirection.HasValue)
originalToolStripDropDownDirection = this.DropDownDirection;
this.DropDownDirection = originalToolStripDropDownDirection.Value == ToolStripDropDownDirection.Left ? ToolStripDropDownDirection.Right : ToolStripDropDownDirection.Left;
}
}
The code of #David does not fix if the menu is opened in the left side of second screen. I have improved that code to work on all screen corner.
private void subMenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem mnuItem = sender as ToolStripMenuItem;
if (mnuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
//get position of current menu item
var pos = new Point(mnuItem.GetCurrentParent().Left, mnuItem.GetCurrentParent().Top);
// Current bounds of the current monitor
Rectangle bounds = Screen.GetWorkingArea(pos);
Screen currentScreen = Screen.FromPoint(pos);
// Find the width of sub-menu
int maxWidth = 0;
foreach (var subItem in mnuItem.DropDownItems)
{
if (subItem.GetType() == typeof(ToolStripMenuItem))
{
var mnu = (ToolStripMenuItem) subItem;
maxWidth = Math.Max(mnu.Width, maxWidth);
}
}
maxWidth += 10; // Add a little wiggle room
int farRight = pos.X + mnuMain.Width + maxWidth;
int farLeft = pos.X - maxWidth;
//get left and right distance to compare
int leftGap = farLeft - currentScreen.Bounds.Left;
int rightGap = currentScreen.Bounds.Right - farRight;
if (leftGap >= rightGap)
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
I did not try the solution by tombam. But since the others didn't seem to work, I came up with this simple solution:
private void MenuDropDownOpening(object sender, EventArgs e)
{
var menuItem = sender as ToolStripDropDownButton;
if (menuItem == null || menuItem.HasDropDownItems == false)
return; // not a drop down item
// Current bounds of the current monitor
var upperRightCornerOfMenuInScreenCoordinates = menuItem.GetCurrentParent().PointToScreen(new Point(menuItem.Bounds.Right, menuItem.Bounds.Top));
var currentScreen = Screen.FromPoint(upperRightCornerOfMenuInScreenCoordinates);
// Get width of widest child item (skip separators!)
var maxWidth = menuItem.DropDownItems.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var farRight = upperRightCornerOfMenuInScreenCoordinates.X + maxWidth;
var currentMonitorRight = currentScreen.Bounds.Right;
menuItem.DropDownDirection = farRight > currentMonitorRight ? ToolStripDropDownDirection.Left :
ToolStripDropDownDirection.Right;
}
Note that in my world, I was not concerned about multiple levels of cascading menus (as in the OP), so I did not test my solution in that scenario. But this works correctly for a single ToolStripDropDownButton on a ToolStrip.

How can I instantiate large number of buttons in Windows Forms?

I'm developing a theatre reservation software. I'm using Windows Forms, the seats is represented by a 2-dimensioned array. And I draw the buttons as following:
public void DrawSeats()
{
// pnl_seats is a Panel
pnl_seats.Controls.Clear();
// Here I store all Buttons instance, to later add all buttons in one call (AddRange) to the Panel
var btns = new List<Control>();
// Suspend layout to avoid undesired Redraw/Refresh
this.SuspendLayout();
for (int y = 0; y < _seatZone.VerticalSize; y++)
{
for (int x = 0; x < _seatZone.HorizontalSize; x++)
{
// Check if this seat exists
if (IsException(x, y))
continue;
// Construct the button with desired properties. SeatSize is a common value for every button
var btn = new Button
{
Width = SeatSize,
Height = SeatSize,
Left = (x * SeatSize),
Top = (y * SeatSize),
Text = y + "" + x,
Tag = y + ";" + x, // When the button clicks, the purpose of this is to remember which seat this button is.
Font = new Font(new FontFamily("Microsoft Sans Serif"), 6.5f)
};
// Check if it is already reserved
if (ExistsReservation(x, y))
btn.Enabled = false;
else
btn.Click += btn_seat_Click; // Add click event
btns.Add(btn);
}
}
// As said before, add all buttons in one call
pnl_seats.Controls.AddRange(btns.ToArray());
// Resume the layout
this.ResumeLayout();
}
But already with a seat zone of 20 by 20 (400 buttons), it spent almost 1 minute to draw it, and in debug I checked that the lack of performance, is the instantiation of the buttons.
There is a way to make it faster? Perhaps disable all events during the instatiation or another lightweight Control that has the Click event too?
UPDATE:
lbl was a test, the correct is btn, sorry.
UPDATE 2:
Here is the IsException and ExistsReservations methods:
private bool IsException(int x, int y)
{
for (var k = 0; k < _seatsExceptions.GetLength(0); k++)
if (_seatsExceptions[k, 0] == x && _seatsExceptions[k, 1] == y)
return true;
return false;
}
private bool ExistsReservation(int x, int y)
{
for (var k = 0; k < _seatsReservations.GetLength(0); k++)
if (_seatsReservations[k, 0] == x && _seatsReservations[k, 1] == y)
return true;
return false;
}
Suppose that you change your arrays for reservations and exclusions to
public List<string> _seatsExceptions = new List<string>();
public List<string> _seatsReservations = new List<string>();
you add your exclusions and reservations in the list with something like
_seatsExceptions.Add("1;10");
_seatsExceptions.Add("4;19");
_seatsReservations.Add("2;5");
_seatsReservations.Add("5;5");
your checks for exclusions and reservations could be changed to
bool IsException(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsExceptions.Contains(key);
}
bool ExistsReservation(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsReservations.Contains(key);
}
of course I don't know if you are able to make this change or not in your program. However consider to change the search on your array sooner or later.
EDIT I have made some tests, and while a virtual grid of 20x20 buttons works acceptably well (31 millisecs 0.775ms on average), a bigger one slows down noticeably. At 200x50 the timing jumps to 10 seconds (1,0675 on average). So perhaps a different approach is needed. A bound DataGridView could be a simpler solution and will be relatively easy to handle.
I also won't use such a myriad of controls to implement such a thing. Instead you should maybe create your own UserControl, which will paint all the seats as images and reacts on a click event.
To make it a little easier for you i created such a simple UserControl, that will draw all the seats and reacts on a mouse click for changing of the state. Here it is:
public enum SeatState
{
Empty,
Selected,
Full
}
public partial class Seats : UserControl
{
private int _Columns;
private int _Rows;
private List<List<SeatState>> _SeatStates;
public Seats()
{
InitializeComponent();
this.DoubleBuffered = true;
_SeatStates = new List<List<SeatState>>();
_Rows = 40;
_Columns = 40;
ReDimSeatStates();
MouseUp += OnMouseUp;
Paint += OnPaint;
Resize += OnResize;
}
public int Columns
{
get { return _Columns; }
set
{
_Columns = Math.Min(1, value);
ReDimSeatStates();
}
}
public int Rows
{
get { return _Rows; }
set
{
_Rows = Math.Min(1, value);
ReDimSeatStates();
}
}
private Image GetPictureForSeat(int row, int column)
{
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
return Properties.Resources.emptySeat;
case SeatState.Selected:
return Properties.Resources.choosenSeat;
default:
case SeatState.Full:
return Properties.Resources.fullSeat;
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
var row = (int)(e.X / widthPerSeat);
var column = (int)(e.Y / heightPerSeat);
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
_SeatStates[row][column] = SeatState.Selected;
break;
case SeatState.Selected:
_SeatStates[row][column] = SeatState.Empty;
break;
}
Invalidate();
}
private void OnPaint(object sender, PaintEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
for (int row = 0; row < Rows; row++)
{
for (int column = 0; column < Columns; column++)
{
var seatImage = GetPictureForSeat(row, column);
e.Graphics.DrawImage(seatImage, row * widthPerSeat, column * heightPerSeat, widthPerSeat, heightPerSeat);
}
}
}
private void OnResize(object sender, System.EventArgs e)
{
Invalidate();
}
private void ReDimSeatStates()
{
while (_SeatStates.Count < Rows)
_SeatStates.Add(new List<SeatState>());
if (_SeatStates.First().Count < Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count < Columns)
columnList.Add(SeatState.Empty);
while (_SeatStates.Count > Rows)
_SeatStates.RemoveAt(_SeatStates.Count - 1);
if (_SeatStates.First().Count > Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count > Columns)
columnList.RemoveAt(columnList.Count - 1);
}
}
This will currently draw forty rows and columns (so there are 800 seats) and you can click on each seat to change its state.
Here are the images i used:
EmtpySeat:
ChoosenSeat:
FullSeat:
If you anchor this control and resize it or you click on a seat to change its state there can be some minor lacking for the repainting if you further increase the number of rows or columns, but that is still somewhere far below one second. If this still hurts you, you have to improve the paint method and maybe check the ClipRectangle property of the paint event and only paint the really needed parts, but that's another story.
Rather than using actual button controls, just draw the image of the seats then when the user clicks on a seat translate the mouse X,Y coordinates to determine which seat was clicked. This will be more efficient. Of course, the drawback is that you have to write the method to translate x,y coordinates to a seat, but that really isn't that difficult.
EDIT; it has been pointed out to me this will not work in Windows Forms!
Well, you are Sequentially working through it.
if one iteration costs 1 sec, the full process will take 400*1 in time.
Perhaps you should try and make a collection of your objects, and process it 'parallel'.
try the .Net framework (4 and above) 'parallel foreach' method:
http://msdn.microsoft.com/en-s/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
edit: so, if you have a list buttonnames, you can say
buttonNames.ForEach(x=>CreateButton(x));
while your CreateButton() method is as following:
private Button CreateButton(string nameOfButton) { Button b = new
Button(); b.Text = nameOfButton; //do whatever you want...
return b; }

Remove controls of flowlayoutpanel and recreating it in C#

I had been experimenting on writing a code to generate images inside a FlowLayoutPanel.
This is what i had done so far, when i click on a button for the first time (by using a checkboxes - read in number of images to open), it will generate the images, when i click on the button on second try, it will not update the flowlayoutpanel.
Even though i tried to remove the controls inside the FlowLayoutPanel, it still doesn't show the second try of the images.
This is the code snippet of the method:
FlowLayoutPanel fwPanel = null;
private void btnOpenFile_Click(object sender, EventArgs e)
{
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
//create a new FLP
fwPanel = new FlowLayoutPanel();
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
//each checked item would be stored into an arraylist
foreach(object itemChecked in clbFile.CheckedItems)
{
listOfFile.Add((clbFile.Items.IndexOf(itemChecked)+1).ToString());
}
int noOfCheckedFile = listOfFile.Count;
PictureBox[] listOfPicture = new PictureBox[noOfCheckedFile];
int positionX = 0, positionY = 0;
int maxPaddingX = (width * MATRIX_SIZE) - 1;
int maxPaddingY = (height * MATRIX_SIZE) - 1;
//dynamically create images.
for (int i = 0; i < noOfCheckedFile; i++)
{
listOfPicture[i] = new PictureBox();
listOfPicture[i].Image = resizeImage((Image)show_picture(Convert.ToInt32(listOfFile[i])), new Size(width, height));
listOfPicture[i].Size = new Size(width, height);
if (positionX > maxPaddingX)
{
positionX = 0;
positionY += height;
}
if (positionY > maxPaddingY)
{
positionY = 0;
}
listOfPicture[i].Location = new Point(positionX,positionY);
listOfPicture[i].Visible = true;
fwPanel.Controls.Add(listOfPicture[i]);
positionX += width;
}
}
show_picture is a method that takes in and integer and returns a bitmap image.
listOfFile is to trace which file to return.
listOfPicture is to store each images.
i tried replacing this lines
//if there is content inside the flowpanel, dump it
if (fwPanel != null)
{
listOfFile.Clear();
}
i have added this line into it, when i do a second click, everything just gone missing, but this is not what i want, because it does not re-populating the FlowLayoutPanel.
if (fwPanel != null)
{
fwPanel.SuspendLayout();
if (fwPanel.Controls.Count > 0)
{
for (int i = (fwPanel.Controls.Count - 1); i >= 0; i--)
{
Control c = fwPanel.Controls[i];
c.Dispose();
}
GC.Collect();
}
fwPanel.ResumeLayout();
listOfFile.Clear();
}
I also tried this, but on second click, nothing will happen.
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
I wonder if i miss out anything, can someone enlighten me on what did i miss out ? Or guide me for the best practice of doing this.
as Suggested, i shifted the creation outside (credit to Sinatr for spotting it)
FlowLayoutPanel fwPanel = new FlowLayoutPanel();
private void createFLP()
{
int panelWidth = width * 4 + 50;
int panelHeight = height * 4 + 50;
fwPanel.Size = new Size(panelWidth, panelHeight);
fwPanel.Location = new Point(0, 0);
this.Controls.Add(fwPanel);
}
that solves the nothing happen part. Followed by using this to remove controls
if (fwPanel != null)
{
List<Control> listControls = fwPanel.Controls.Cast<Control>().ToList();
foreach (Control control in listControls)
{
fwPanel.Controls.Remove(control);
control.Dispose();
}
listOfFile.Clear();
}
and everything works like a charm, hope that this answer will be able to help others who are facing the same problem too.

problems updating picture box control

I have two controls on the same form. Both controls contain an ObjectListView control. The one listview was created with the graphical editor in visual studio. This one is not causing any issues. The other listview is created programmatically at run-time. I have defined an event handler for each control that gets called when the hot item changes and they are both firing when they should. Both event handlers call the same code to update a picturebox control. The problem is that the picturebox does not get updated when the programmatically defined listview is asking it to. I am positive the event handler is getting called because my code writes to a text file as well as updating the picture box. The text file gets updated but the picture box does not. I have tried updating, invalidating, and refreshing the PicutureBox as well as the parent form, but I just can not get it to update.
I am not sure if this is an ObjectListView issue or a standard WinForms problem. I realize my question is very vague but I am not sure how to clarify it without posting all my code. Any advice would be appreciated.
Here is the code that the event handler calls:
public void ShowBitmap(object sender, HotItemChangedEventArgs e, ObjectListView lv, string type)
{
ObjectListView olv = sender as ObjectListView;
if (sender == null)
{
return;
}
switch (e.HotCellHitLocation)
{
case HitTestLocation.Nothing:
break;
case HitTestLocation.Group:
break;
case HitTestLocation.GroupExpander:
break;
default:
if (e.HotColumnIndex == 0)
{
pictureBox1.Hide();
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
int rowIndex = e.HotRowIndex;
string text = "";
if (type == "Main Parts")
{
TypedObjectListView<MainRadanProjectPartsPart> tlist = new TypedObjectListView<MainRadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Parts")
{
TypedObjectListView<RadanProjectPartsPart> tlist = new TypedObjectListView<RadanProjectPartsPart>(lv);
text = tlist.Objects[rowIndex].Symbol;
}
else if (type == "Nests")
{
TypedObjectListView<MainRadanProjectNestsNest> tlist = new TypedObjectListView<MainRadanProjectNestsNest>(lv);
text = tlist.Objects[rowIndex].FileName;
}
if (text != null)
{
Point screenCoords = Cursor.Position;
Point controlRelatedCoords = lv.PointToClient(screenCoords);
if (controlRelatedCoords.Y < oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
int yPos = controlRelatedCoords.Y + 60;
pictureBox1.Location = new Point(xPos, yPos);
}
else if (controlRelatedCoords.Y > oldCursorPosition.Y)
{
pictureBox1.Location = controlRelatedCoords;
int xPos = controlRelatedCoords.X;
//int yPos = controlRelatedCoords.Y - pictureBox1.Height;
int yPos = controlRelatedCoords.Y - pictureBox1.Height + 30;
pictureBox1.Location = new Point(xPos, yPos);
}
pictureBox1.Show();
pictureBox1.BringToFront();
olvTreeViewMainParts.Focus();
lv.Focus();
pictureBox1.Visible = true;
DrawSymbol(text);
oldCursorPosition = controlRelatedCoords; // save the cursor position to track cursor direction between calls
}
else
{
DrawSymbol("");
}
}
else
{
pictureBox1.Hide();
}
break;
}
}
Here is the event handler for the programmaticaly defined listview:
// track the cursor as it moves over the items in the listview
private void olvPartsListView_HotItemChanged(object sender, HotItemChangedEventArgs e)
{
ShowBitmap(sender, e, olvPartsListView, "Parts");
}

How to properly change the image of a button I just clicked?

Here is my dilemma. I have a set of 10 buttons that I use to rate something. They have a star image that when pressed gets changed from a grey one to a red one. If I press the star number 5 all the previous ones also get changed to red.
My problem is that the star that is clicked does not change its image so I found a workaround to introduce a pause inside a dispatcher block. I don't see this very elegant but it works and I was wondering if someone has a better approach.
Even if nobody finds a better way at least this code will help other people to do stuff with multiple buttons that do almost the same thing or browsing through controls in a panel.
Here is the code of the click event:
private void btnStar_Click(object sender, RoutedEventArgs e)
{
//First we get the number of the star from the control name
String strNum = (sender as Button).Name;
strNum = strNum.Replace("btnStar", "");
int idxSelected = int.Parse(strNum);
Debug.WriteLine("Selected star #" + strNum);
//We store the image ON an OFF to be used later
ImageBrush imgbrON = new ImageBrush();
imgbrON.ImageSource = new BitmapImage(new Uri("images/estrella_on.png", UriKind.Relative));
imgbrON.Stretch = Stretch.None;
ImageBrush imgbrOFF = new ImageBrush();
imgbrOFF.ImageSource = new BitmapImage(new Uri("images/estrella_off.png", UriKind.Relative));
imgbrOFF.Stretch = Stretch.None;
//If we pressed the first star when only the first was selected we reset all
if (idxSelected == 1 && iCurrentNumberOfStars == 1)
{
idxSelected = 0; //In order to deselect all stars
Debug.WriteLine("Deselect all");
}
else
{
//Here is the code to do the WORKAROUND to select the clicked star
Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(500);
(sender as Button).Background = imgbrON;
});
}
iCurrentNumberOfStars = idxSelected;
foreach (UIElement child in ContentPanel.Children)
{
Thread.Sleep(10);
if (child.GetType().Name == "Button")
{
Button tmpButton = (child as Button);
Image content = tmpButton.Content as Image;
strNum = tmpButton.Name;
if (strNum.StartsWith("btnStar") == true)
{
strNum = strNum.Replace("btnStar", "");
int idxtmp = int.Parse(strNum);
if (idxtmp > idxSelected )
{
Debug.WriteLine(tmpButton.Name + ":OFF");
tmpButton.Background = imgbrOFF;
}
else
{
Debug.WriteLine(tmpButton.Name +":ON");
tmpButton.Background = imgbrON;
}
}
}
}
}
}
The reason why it's happens - Button has a visual states which manipulate the background property. When you try to set the background to your own - animation overrides your changes. If you remove background animation from visual states - it will work without a Delay.

Categories