My son and I are working on a hobby project together (Winform app) for the dice game of Farkle and need guidance on handling dragdrop events for the dice. Please note we are not looking for the answer, or the code; just some general ideas on solution attack.
Here are the constructs:
RolledDice
—We have a single form with two panels. One panel contains 6 PictureBoxes which display dice images from an ImageList based on a DiceRoller class we built to generate random integers from 1 to 6. We are using a backing PictureBox array to iterate over each of the PictureBoxes. The click event for a “Roll Dice” button displays the rolled dice—all is good, this works great.
PlayerDice
—The second panel is configured identically to the first one and accepts user selected dice dragged from the Rolled Dice panel. Our use case requires the ability for the user to drag dice from the Rolled Dice panel to the Player Dice panel, and back again if the user changes their mind about the dice they want to keep—all is good, this works great.
Problem Statement
—Although we can drag dice from the Rolled Dice panel to the Player Dice panel (and update the backing PictureBox arrays in the process), it seems necessary to have three event handlers for each of the 6 PictureBoxes in both panels (MouseDown, DragEnter and DragDrop), and this amounts to a ton of code.
Question
—Is there an elegant way to have one set of these 3 event handlers for ALL Rolled Dice and one set of these event handlers for ALL Player Dice, rather than having a bunch of stringy code like we have now?
Again, we are not looking for the exact answer or the code, just some general ideas on solution attack.
EDITED:
Here is the code we have for ONE image.
#region Mouse and Drag Events
// Mouse and Drag Events for ONE Rolled Dice
void pbRolled1_MouseDown(object sender, MouseEventArgs e)
{
PictureBox source = (PictureBox)sender;
DoDragDrop(source.Image, DragDropEffects.Move);
}
void pbRolled1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Bitmap))
e.Effect = DragDropEffects.Move;
}
void pbRolled1_DragDrop(object sender, DragEventArgs e)
{
PictureBox destination = (PictureBox)sender;
destination.Image = (Bitmap)e.Data.GetData(DataFormats.Bitmap);
}
// Mouse and Drag Events for ONE Player Dice
void pbPlayer1_MouseDown(object sender, MouseEventArgs e)
{
PictureBox source = (PictureBox)sender;
DoDragDrop(source.Image, DragDropEffects.Move);
}
void pbPlayer1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Bitmap))
e.Effect = DragDropEffects.Move;
}
void pbPlayer1_DragDrop(object sender, DragEventArgs e)
{
PictureBox destination = (PictureBox)sender;
destination.Image = (Bitmap)e.Data.GetData(DataFormats.Bitmap);
}
#endregion
You don't necessarily need to have a 1-to-1 relationship between controls and their events - events may be shared between controls.
Since you don't want a specific answer, I'll give you a general example. Take this simple form, with three buttons and a label:
Now, the code for this simple form is as follows (Form1.cs):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button1.Click += new System.EventHandler(this.button_Click);
this.button2.Click += new System.EventHandler(this.button_Click);
this.button3.Click += new System.EventHandler(this.button_Click);
}
private void button_Click(object sender, EventArgs e)
{
Button button = (sender as Button);
if (button != null)
{
label1.Text = string.Format("You pressed {0}", button.Text);
}
}
}
You could add the events in Design mode, and select the same event for each. I hooked up the events in the constructor just to make it a little more obvious example.
Notice that all three button click handlers point to a single event handler. That handler will look at the sender of the event to see which button was pressed. It then just takes the caption of the button, and displays it in a message in the label.
You can do the similar things with the duplicate events you are creating now (especially after looking at the code you added to your question).
Well the mouse events are handled by the Windows form so that implementation will need to be done on the form but for the drag events you could have your picture boxes implement the IDropTarget interface so that code could be consolidated and give you some reuse and cleaner code.
Related
To create a mouse over button I use this code
private void btnCreateAccount_MouseHover(object sender, EventArgs e)
{
btnCreateAccount.ForeColor = Color.Gold;
}
private void btnCreateAccount_MouseLeave(object sender, EventArgs e)
{
btnCreateAccount.ForeColor = Color.Black;
}
The mouse over button works however when I hover over the button there is a good at least 1 second delay. I would think that it should change colour as soon as the mouse is placed over the button and not with a (in my opinion) too long delay.
Is there any way of fixing that code by like refreshing the button or something along those lines? or perhaps someone has a code that works perfectly?
You are handling the Mouse Hover event. This will require the cursor to be still for a short while in order to fire.
The pause required for this event to be raised is specified in milliseconds by the MouseHoverTime property.
This is read only.
Normally if you want the colour to change immediately you should handle the Mouse Enter event:
private void btnCreateAccount_MouseEnter(object sender, EventArgs e)
{
btnCreateAccount.ForeColor = Color.Gold;
}
I'm trying to build a simple interface that allows users to drop files into a listBox to add them to a process, and to drag them out to remove them. Everything is working fine, but I'd like to add one feature to make it just a tad more sophisticated.
Right now, I have the removal of the item tied to the DragLeave event, which means that as soon as the mouse leaves the box, the item is removed. But I'd like for users to be able to change their minds. In other words, if they realize they're dragging the wrong file out, I'd like them to be able to move the mouse back into the listBox and release the mouse to cancel the action. I'm thinking that means I need to be able to capture the MouseUp event instead of the DragLeave event. But that hasn't been successful so far.
Below is the code I'm currently using for removing files dragged out. How can I modify to keep the files from being removed form the list until the user lets the mouse button go?
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb.Items.Remove(lb.SelectedItem);
}
Edit 2013/05/16
The comments and answers so far have been useful, but I realize my question isn't clear enough. In this case, I'm displaying a dialog separate from the parent form that is basically as big as the listBox. When someone drags a file out of the list, they're dragging it off the form completely. Have I backed myself into a corner by doing this? I recognize I'm making it harder than it has to be, but I'd still like to see how it would work if it's possible.
Here's a fairly quick hack approach to gaining the functionality you want:
public object lb_item = null;
private void listBox1_DragLeave(object sender, EventArgs e)
{
ListBox lb = sender as ListBox;
lb_item = lb.SelectedItem;
lb.Items.Remove(lb.SelectedItem);
}
private void listBox1_DragEnter(object sender, DragEventArgs e)
{
if (lb_item != null)
{
listBox1.Items.Add(lb_item);
lb_item = null;
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
lb_item = null;
if (listBox1.Items.Count == 0)
{
return;
}
int index = listBox1.IndexFromPoint(e.X, e.Y);
string s = listBox1.Items[index].ToString();
DragDropEffects dde1 = DoDragDrop(s, DragDropEffects.All);
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
lb_item = null;
}
Every time the user drags an item out of the box, it's saved temporarily until the user drops it somewhere else or mouses down on a new item in the list.
Note the important part of this is detecting when and where the user let's go of that mouse, which is the rationale behind handling the DragDrop event of Form1, the parent of listBox1.
Depending on the sophistication and density of the rest of your layout, where you handle DragDrop could be much different for you. This is why it's kind of "hacky", but it's also quite simple. It shouldn't matter, though, where or how many times you null lb_item since it pertains only to that specific ListBox.
I suppose another way to do it would be to track the user's mouse states and act accordingly, which may be more appropriate for you if it's inconceivable to handle a lot of DragDrop stuff.
EDIT: If you wanted to be REAL thorough, you could enumerate through every control of the base form using foreach and programmatically append a handler for the DragDrop event to that control, then remove it when done... but that may be getting a little nutty. I'm sure someone has a better approach.
Say I have a Form which we'll call it ParentForm, and it contains a Panel which we'll call ContainerPanel. Now, ContainerPanel contains a Panel, which we'll call EntityPanel.
So basically, the Form contains A Panel which contains a Panel.
In ContainerPanel, we have:
void EntityPanel_MouseDown(object sender, MouseEventArgs e)
{
ContainerPanel.Controls.Remove(EntityPanel);
ParentForm.AcceptEntityPanel(EntityPanel);
}
and in MainForm, we have:
void AcceptEntityPanel(Panel panel)
{
Controls.Add(panel);
panel.MouseUp += new MouseEventHandler(
delegate(object sender, MouseEventArgs e)
{
MessageBox.Show("Mouse has been released.");
});
}
Note: This is example code only, which I have just typed in here without verifying syntax, etc. I realise it is trivial to combine these two functions into one, however in my application these two functions do several other things and should be separate.
So the EntityPanel, which belongs to ContainerPanel needs to be transferred to ParentForm when the user presses the mouse down.
When the user releases the mouse, I still need the MouseUp event to be triggered, but it is not working.
Previously, I was passing information about the panel and creating a new panel on the parent form, and manually calling the MouseDown method.
What I'm doing now, as you can see in my above example, is that I'm passing the exact same panel back to the parent form. I had hoped that this way the MouseDown/MouseUp would work, however it didn't.
I'm running out of ideas on how else to structure this module of my program.
Any ideas?
This works for me:
void Form1_Load(object sender, EventArgs e)
{
var innerPanel = new Panel();
outerPanel.Controls.Add(innerPanel);
innerPanel.MouseDown += (a,b) =>
{
outerPanel.Controls.Remove(innerPanel);
Controls.Add(innerPanel);
innerPanel.MouseUp += (x,y) => MessageBox.Show("!");
};
}
I am new to using Event Handling in C# .NET, and I am trying to understand the behavior behind some simple code that I am experimenting with. I am working with a more complicated example, but I am hoping I will get a more focused answer if I simplify the example.
I have the following code which defines a main window with a ListBox that is initialized with values, and a panel in the window. I am working with dragging the ListBox Items and dropping them in the panel. To signify that the panel is reading the DragDrop event, I am simply just changing the background color.
My problem is, it is not changing the background color when I drop the values, hence, the DragDrop is not working. I know this is a bit exaggerated, but I am trying to understand why its not working.
Here is the following code that I am using.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//Allow Panel to accept dropped values
this.panel1.AllowDrop = true;
//Initialize ListBox with sample values
listBox1.Items.Add("First Name");
listBox1.Items.Add("Last Name");
listBox1.Items.Add("Phone");
//Setup DragDrop Event Handler - is this correct, or even needed?
this.panel1.DragDrop += new DragEventHandler(panel1_DragDrop);
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
ListBox box = (ListBox)sender;
String selectedValue = box.Text;
DoDragDrop(selectedValue.ToString(), DragDropEffects.Copy);
}
private void panel1_DragDrop(object sender, DragEventArgs e)
{
//Change Background color to signify value has been dropped
((Panel)sender).BackColor = Color.Black;
}
}
I realize this is an oversimplified example. If you see what I am doing wrong, then please let me know.
EDIT To give an example of why I am confused, I change this example around to put the dragged ListBox Item text into a Textbox when the DragOver event was fired, and it worked fine, but when I tried to do the same thing when they dropped the values over the textbox, I could not get it to work.
Handle the panel's DragEnter event and set e.Effects to something other than None.
I was using an Adobe product (If I remember correctly, it was a PDF Creator) the other day, and they have this cool feature where you can literally click anywhere inside the window and just start typing. And the text will appear as you type, whereever you clicked.
Can somebody please point me in the right direction where I can learn how to implement something like this?
Thank you
Edit:
Steps to Click & Type:
Capture MouseDown and store location where user clicked.
Handle KeyDown event to see what key was pressed.
Foreach key that is pressed, create a label and place it next to the last-typed character.
#Qua, in the last step, am I right in thinking that? If so, how would I account for the length of an individual character, so I know where to place the next one? I think I would need to somehow measure the type and size of the font in the label and adjust positioning accordingly...?
You could simply draw a new TextBox on your Windows Form when the user clicks. The MouseClick event will expose all the properties you need to draw the textbox right where they click.
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
TextBox txtBox = new TextBox();
txtBox.Location = new Point(e.X, e.Y);
this.Controls.Add(txtBox);
txtBox.Focus();
}
You could make the TextBox transparent or what not if you wanted to mask the fact that they're typing a normal TextBox.
Lets say that you wanted to implement this feature on a usercontrol that you were making. At first you would need to handle the mouse down event to register where the user clicked, and after this you should be able to handle the key down event to register which keys the user pressed.
To draw the actual text at the target location you have several options. You could use GDI+ to render the strings on the control or you could insert labels which would benefit you due to the amount of functionality that the labels come with.
Here's a prototype example of how it could be implemented using GDI:
private Point? lastSelected;
private Dictionary<Point, string> renderedText = new Dictionary<Point, string>();
private Point LastSelected
{
get
{
return (Point)lastSelected;
}
}
private void Form1_Load(object sender, EventArgs e)
{
this.MouseDown += Form1_MouseDown;
this.KeyDown += Form1_KeyDown;
this.Paint += Form1_Paint;
}
void Form1_Paint(object sender, PaintEventArgs e)
{
foreach (KeyValuePair<Point, string> pair in renderedText)
{
e.Graphics.DrawString(pair.Value, new Font("Arial", 12), Brushes.Black,
pair.Key);
}
}
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (lastSelected != null)
{
if (!renderedText.ContainsKey(LastSelected))
{
renderedText.Add(LastSelected, "");
}
renderedText[LastSelected] = renderedText[LastSelected] + e.KeyCode;
this.Invalidate();
}
}
void Form1_MouseDown(object sender, MouseEventArgs e)
{
lastSelected = e.Location;
}
Reply to comment:
The above code captures the location of the mouse when the user clicks on the form and saves it in the lastSelected variable. At each subsequent key press on the form the key pressed is appended to the string representing that location. Furthermore the strings are being rendered on the screen at the captured location in the paint. The strings are rendered using GDI+, which means that you don't need to worry about the length of the individual characters.
Note that for a real application storing the locations and strings via a hashtabel (dictionary in C#) is not a great idea. You should rather create a custom class or structure that will hold the information such as location, text ect. This will allow you to further improve the functionality by adding options for stuff like bolding, italic, font sizes and so on.