I'm completely new to GUI programming and need a little help with a list of pictureboxes.
The idea is that I have a list of pictureboxes. When a user clicks on one I want to (for example) change the BorderStyle property of the one selected to be Fixed3D, but change the remaining collection borders to FixedSingle (or something like that). What's the proper way to do something like this? I guess the bigger picture is how do I get a method of one class to call a method of another without having any information about it?
class myPicture
{
private int _pictureNumber;
private PictureBox _box;
public myPicture(int order)
{
_box = new List<PictureBox>();
_box.Click += new System.EventHandler(box_click);
_pictureNumber = order;
}
public void setBorderStyle(BorderStyle bs)
{
_box.BorderStyle = bs;
}
public void box_click(object sender, EventArgs e)
{
//here I'd like to call the set_borders from myPicturesContainer, but I don't know or have any knowledge of the instantiation
}
}
class myPicturesContainer
{
private List<myPicture> _myPictures;
//constructor and other code omitted, not really needed...
public void set_borders(int i)
{
foreach(myPicture mp in _MyPictures)
mp.setBorderStyle(BorderStyle.FixedSingle);
if(i>0 && _MyPictures.Count>=i)
_MyPictures[i].setBorderStyle(BorderStyle.Fixed3d);
}
}
You will need to create a Clicked event in your myPicture class and raise that event when it is clicked. Then you will need to attach to this event in your myPicturesContainer for each instance of myPicture that you have.
Here is a very simple example of what I mean:
class myPicture
{
public event Action<Int32> Clicked = delegate { };
private int _pictureNumber;
public void box_click(object sender, EventArgs e)
{
this.Clicked(this._pictureNumber);
}
}
class myPicturesContainer
{
private List<myPicture> _myPictures;
public void set_borders(int i)
{
foreach (myPicture mp in _myPictures)
{
mp.Clicked += pictureClick;
}
}
void pictureClick(Int32 pictureId)
{
// This method will be called and the pictureId
// of the clicked picture will be passed in
}
}
Related
I'm fairly new to C# and I'm trying to create a Hangman game in WinForms, I've got the game functionality working, but I'm trying to create a form where the user selects a category and then the word to guess is from the category selected.
I've got a HangEventArgs like below:
public class HangEventArgs : EventArgs
{
public Category WordCategory { get; set; }
}
and a class for the data (I'm hoping to expand it to add more features in the future).
public enum Category
{
// Categories are stores here
}
public class HangData
{
public Category WordCategory { get; protected set; }
public HangData(Category askWhat)
{
WordCategory = askWhat;
}
}
And a class where the words are stored
public static class WordsToGuess
{
public static string[] Capitals =
{
"London",
"Paris" // more words here
}; // more categories here
Finally I have my button click event for all the categories, I've created my own Button as to not use the default EventArgs.
private void bCategory_Click(object sender, HangEventArgs e)
{
MainGame mg = new MainGame(new HangData(e.WordCategory));
mg.ShowDialog();
}
I've been trying to use event handlers like so
public event EventHandler<HangEventArgs>(object sender, HangEventArgs e);
But I'm not sure the proper way to implement this into my code.
If I use
bCapitals.Click += new EventHandler(bCategory_Click);
I get a no overload matches delegate error and I'm stuck on how to fix it. Thanks for the help in advance.
Create your category button like this:
public class CategoryButton : Button
{
protected override void OnClick(EventArgs e)
{
// Just discard the `e` argument and pass your own argument.
base.OnClick(new HangEventArgs { WordCategory = Category.Cities });
}
}
Subscribe the event with:
categoryButton1.Click += CategoryButton1_Click;
Use like this
private void CategoryButton1_Click(object sender, EventArgs e)
{
if (e is HangEventArgs hangEventArgs) {
MessageBox.Show(hangEventArgs.WordCategory.ToString());
}
}
Note that the click mechanism still works as expected. You don't need to fire the event yourself.
Of course you could create your own event; however, then, it must have a different name like HangClick and you must fire it yourself.
public class CategoryButton : Button
{
public event EventHandler<HangEventArgs> HangClick;
protected virtual void OnHangClick(HangEventArgs e)
{
HangClick?.Invoke(this, e);
}
protected override void OnClick(EventArgs e)
{
OnHangClick(new HangEventArgs { WordCategory = Category.Cities });
// Optionally, if you want to preserve the standard click event behaviour:
base.OnClick(e);
}
}
Subscribe with:
categoryButton1.HangClick += CategoryButton1_HangClick;
Use like this:
private void CategoryButton1_HangClick(object sender, HangEventArgs e)
{
MessageBox.Show(e.WordCategory.ToString());
}
It's known that there are some solutions similar to this one, but I can't solve my problem with them.
I have two user controls:
The first one makes a Report object.
The second one shows it.
I have a main Form that links both controls.
These two controls are created in a DLL, and are added to the
main form like this:
//ADDS THE FIRST CONTROL TO THE PANEL CONTROL
myDll.controlUserReport userControlA = new myDll.controlUserReport();
panelControl1.Controls.Add(userControlA);
userControlA.Dock = DockStyle.Left;
//ADDS THE SECOND CONTROL TO THE PANEL CONTROL
myDll.controlDocViewer userControlB = new myDll.controlDocViewer();
panelControl1.Controls.Add(userControlB);
userControlB.Dock = DockStyle.Fill;
How can I pass the Report object, which is created in the first control controlUserReport when I click over a button, to the other user control controlDocViewer to show it?
You should use events for this. In UserControlA declare the event:
//Declare EventHandler outside of class
public delegate void MyEventHandler(object source, Report r);
public class UserControlA
{
public event MyEventHandler OnShowReport;
private void btnShowReport_Click(object sender, Report r)
{
OnShowReport?.Invoke(this, this.Report);
}
}
In UserControlB subscribe to the event and show the report:
public class UserControlB
{
// Do it in Form_Load or so ...
private void init()
{
userControlA.OnShowReport += userControlA_OnShowReport;
}
private void userControlA_OnShowReport(object sender, Report r)
{
// Show the report
this.ShowReport(r);
}
}
the post above is good except init() method should not be in ControlB, but in the parent form, something like this:
public class frmMain : Form
{
private void frmMain_Load(object sender, EventArgs e)
{
// subscribe/glue
userControlA.OnShowReport += userControlB.OnShowReport;
userControlB.OnShowReport += userControlA.OnShowReport;
}
public class UserControlA
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void some_method(string z, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
public class UserControlB
{
public event EventHandlerNodeCopy OnDataCopy;
public TreeNode NodeCopied { get; set; }
private void another_method(int i, TreeNode trn)
{
OnDataCopy?.Invoke(this, trn);
...
}
public void frmJsTree_OnDataCopy(object source, TreeNode tn)
{
NodeCopied = tn;
}
enter code here
Another approach is using BehaviorSubject (requires System.Reactive). Once the data is added in the BehaviorSubject, all places that subscribe can see the info. A very basic example:
Create a class to represent your data. Ex:
DataService.cs
public static BehaviorSubject<YourDataType> MyAwesomeData { get; } = new BehaviorSubject<YourDataType> (null);
In You UserControlA (maybe in the clickEvent from the button) or whatever:
private void btnShowReport_Click(object sender, MouseButtonEventArgs e)
{
// Do some stuffs to prepare the data...
YourDataType myDataReportPrepared = null; // something;
// Here you update your DataInfo on BehaviorSubject
DataService.MyAwesomeData.OnNext(myDataReportPrepared);
}
Finally every place in your solution that uses subscribe in that Subject will listen to that data, like for example in your UserControlB:
// This will trigger every time MyAwesomeData.OnNext() is executed.
DataService.MyAwesomeData.Subscribe(item =>
{
if (item != null){
// Do something with it...Like populate some Datagrid...
}
});
My class contain many properties and i need to handle each properties.
See this below:
public partial class my_form : Form
{
private Image[] _imagelist;
public Image[] imagelist
{
get
{
return _imagelist;
}
set
{
this._imagelist = value;
this.on_imagelist_changed();
}
}
private void on_imagelist_changed()
{
// do something.
}
private void button1_Click(object sender, EventArgs e)
{
/* set new imagelist */
this.imagelist = getimagelist();
}
}
Yes, It's work fine.
But when i call like this.
public partial class my_form
{
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
int selectedIndex = this.listView1.SelectedItems[0].ImageIndex;
this.imagelist[selectedIndex] = dosomething(this.imagelist[selectedIndex]);
}
}
It's don't call on_imagelist_changed(). Why ?
I can't add property by indexer like this. :(
public partial class my_form
{
public Image imagelist[int x]
{
get
{
return _imagelist[x];
}
set
{
this._imagelist[x] = value;
this.on_imagelist_changed();
}
}
}
Can anyone help me solve this problem ?
Can i avoid to make a control class like this C# Indexers ?
I founded some suggestion, they told me let try ObservableCollection. I don't understand about this. May be someone example for me ?
It's don't call on_imagelist_changed(). Why?
Because, quite bluntly, imageList has not changed. Some images inside imageList may have changed, but your code reacts only to the change of the entire imageList. The assignment calls get of your property twice; it never calls the setter.
I can't add property by indexer like this.
That's correct. However, you correctly noted that you could use an observable collection:
public ObservableCollection<Image> Images {get;}
private void OnImageListChanged(
object sender
, NotifyCollectionChangedEventArgs e) {
// do something.
}
public MyForm() {
Images = new ObservableCollection<Image>();
Images.CollectionChanged += OnImageListChanged;
}
Because your property gets and sets pointer to array, not array itself. So when you change array items pointer stays the same.
That's why properties should not return arrays.
On my form, I have one Panel container, named "panelShowList".
On my project, i added a new class, which look like this:
class myNewClass
{
private int newPanelPos = 30;
private const int spaceBetweenElements = 30;
private const int panelWidth = 90;
private const int panelHeight = 40;
private int elementPos = 0;
private ArrayList myPanels = new ArrayList() { };
// some irelevant methods
public void addElementPanels(Panel dataPanel, Panel nextPanel)
{
myPanels.Add(dataPanel);
myPanels.Add(nextPanel);
}
public void displayPanels()
{
foreach (Panel tmp in myPanels)
{
// here i'm stuck
// i need to do something like this :
// myMainForm.panelShowList.Controls.Add(tmp);
// of course this is wrong! but i need a method to acces that control
}
}
}
Basically, I need a way to add all Panels from my ArrayList on "panelShowList" control from my form.
I tried something like this:
public void displayPanels()
{
frmMain f = new frmMain();
foreach (Panel tmp in myPanels)
{
f.display(tmp);
// where display(Panel tmp) is a function in my Form, who access
// "panelShowList" control and add a new Panel
}
}
But it only works if i do this:
f.ShowDialog();
and another form is open.
Any suggestions will be appreciated.
Maybe a bit late, but by all means, here is another approach, that's still more clean than David's approach:
You should add an EventHandler in your MyNewClass. Then you can subscribe to that event from within your form.
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.DisplayPanelsInvoked += DisplayPanelsInvoked;
}
private void DisplayPanelsInvoked(object sender, DisplayPanelsEventArgs e)
{
var panels = e.Panels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
}
public void DisplayPanels()
{
OnDisplayPanels(new DisplayPanelsEventArgs(_panels));
}
protected virtual void OnDisplayPanels(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = DisplayPanelsInvoked;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> DisplayPanelsInvoked;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> panels)
{
Panels = panels;
}
public IList<Panel> Panels { get; private set; }
}
In my opinion it's a better solution, because you don't need to provide a reference of the form to the MyNewClass instance. So this approach reduces coupling, because only the form has a dependency to the MyNewClass.
If you always want to "update" the form whenever a panel is added, you could remove the DisplayPanels-method and shorten the code to this:
public partial class Form1 : Form
{
private readonly MyNewClass _myNewClass;
public Form1()
{
InitializeComponent();
_myNewClass = new MyNewClass();
_myNewClass.PanelAdded += PanelAdded;
}
private void PanelAdded(object sender, DisplayPanelsEventArgs e)
{
var panels = e.AllPanels; // Add the panels somewhere on the UI ;)
}
}
internal class MyNewClass
{
private IList<Panel> _panels = new List<Panel>();
public void AddPanel(Panel panel)
{
_panels.Add(panel);
OnPanelAdded(new DisplayPanelsEventArgs(_panels, panel)); // raise event, everytime a panel is added
}
protected virtual void OnPanelAdded(DisplayPanelsEventArgs e)
{
EventHandler<DisplayPanelsEventArgs> handler = PanelAdded;
if (handler != null)
{
handler(this, e);
}
}
public event EventHandler<DisplayPanelsEventArgs> PanelAdded;
}
internal class DisplayPanelsEventArgs : EventArgs
{
public DisplayPanelsEventArgs(IList<Panel> allPanels, Panel panelAddedLast)
{
AllPanels = allPanels;
PanelAddedLast = panelAddedLast;
}
public IList<Panel> AllPanels { get; private set; }
public Panel PanelAddedLast { get; private set; }
}
and another form is open
That's because you're creating an entirely new form:
frmMain f = new frmMain();
If you want to modify the state of an existing form, that code will need a reference to that form. There are a number of ways to do this. One could be to simply pass a reference to that method:
public void displayPanels(frmMain myMainForm)
{
foreach (Panel tmp in myPanels)
{
// myMainForm.panelShowList.Controls.Add(tmp);
// etc.
}
}
Then when your main form invokes that method, it supplies a reference to itself:
instanceOfNewClass.displayPanels(this);
Though, to be honest, it's not really clear what sort of structure you're going for here. If code is modifying a form then I imagine that code should be on that form. It can certainly be organized into a class, but perhaps that can be an inner class of that form since nothing else needs to know about it.
I'm also concerned that your implementation of myNewClass requires methods to be invoked in a specific order. Any given operation on an object should fully encapsulate the logic to complete that operation. Some of that initialization logic may belong in the constructor if the object isn't in a valid state until that logic is completed.
This is all a bit conjecture though, since the object structure isn't clear here.
I have a form which I want to be 'resusable' for a variety of situations. Mostly display and print information. The form has 2 buttons and a listbox
I want to be able to pass an object to the form that tells the form what the buttons are to do when pressed(for example show a MessageBox, Print out the contents of the listbox or close the form)
I am using an if statement to figure out what event to assign to my button...is there a better way to do this?
Ideally I would like to set the event from the initial calling code instead fo using an enum called 'Action'
==========calling code=================
var information = new Information();
information.Action = Action.Print;
var frmInformation = new frmInformation(information);
frmInformation.Show(this);
====================information class======================
public class Information
{
public delegate void OkButtonDelegate();
public IList<string> information{ get; set; }
public Information()
{
information = new BindingList<string>();
}
===============information form======================
public partial class frmInformation : Form
{
private readonly Information _information;
public Information.OkButtonDelegate _delegate;
public frmInformation(Information information)
{
_information = information;
InitializeComponent();
SetupForm();
}
private void SetupForm()
{
if (_information.Action== Action.Print)
_delegate = new Information.OkButtonDelegate(Print);
else if (_information.Action == Action.Close)
_delegate = new Information.OkButtonDelegate(Close);
}
private void ShowMessageBox()
{
MessageBox.Show("lalalalalala");
}
public static void Print()
{
//take the contente out of listbox and send it to the printer
}
private void btnSend_Click(object sender, EventArgs e)
{
_delegate();
}
You may change it this way
switch (_information.Action) {
case Action.Print:
btnSend.Click += (s,e) => Print();
break;
case Action.Close:
btnSend.Click += (s,e) => Close();
break;
}
You won't need delegate type, generated Click handler and _delegate variable.