|
Hi. I'm new to C#. I really want to learn to write code in a proper way. In the following example i was trying to do this:
1. On Form there is black Panel
2. On this Panel there are three green PictureBoxes
3. Whenever one of these PicureBoxes is clicked, Panel changes its color to red.
Now, the code I've came up with is:
using System;
using System.Drawing;
using System.Windows.Forms;
public class MainForm : Form
{
public class SomeClass : PictureBox
{
public SomeClass()
{
this.BackColor = Color.Green;
this.MouseDown += new System.Windows.Forms.MouseEventHandler(mouseDown);
}
public void mouseDown(object sender, MouseEventArgs e)
{
panel1.BackColor = Color.Red;
}
}
Panel panel1 = new Panel();
SomeClass someObject = new SomeClass();
SomeClass someObject2 = new SomeClass();
SomeClass someObject3 = new SomeClass();
public MainForm()
{
this.Controls.Add(this.panel1);
this.panel1.Size = new System.Drawing.Size(200, 600);
this.panel1.BackColor = Color.Black;
this.panel1.Controls.Add(this.someObject);
this.panel1.Controls.Add(this.someObject2);
this.panel1.Controls.Add(this.someObject3);
this.someObject.Location = new System.Drawing.Point(0, 0);
this.someObject2.Location = new System.Drawing.Point(0, 100);
this.someObject3.Location = new System.Drawing.Point(0, 200);
}
public static void Main()
{
Application.Run(new MainForm());
}
}
And the big problem is that I cannot access panel1. What's the most elegant solution to this?
|
|
|
|
|
This is already some quite 'special' code, not using the standard Visual Studio WinForms template or its designer.
But without going into that, here are 2 possible solutions to your problem:
- SomeClass is a Control (inherited from PictureBox) that exposes public events:
you can just subscribe to the mousedown event in the MainForm class (just as you set its location):
public class MainForm : Form
{
public MainForm()
{
someObject.MouseDown += mouseDown;
}
private void mouseDown(object sender, MouseEventArgs e)
{
panel1.BackColor = Color.Red;
}
}
2. if you don't want to do it that way, you can 'supply' the panel reference to the constructor of SomeClass:
public class SomeClass : PictureBox
{
Panel panel1;
public SomeClass(Panel mainPanel)
{
panel1 = mainPanel;
}
}
and in the MainForm class you do:
SomeClass someObject = new SomeClass(panel1);
|
|
|
|
|
Since you derive your SomeClass from PictureBox, the best way is is to do it properly: create an event in your SomeClass which indicates to the outside world "I need attention".
Your panel handles that event, and changes itself.
That way, the control doesn't need to know about any containing controls, and the containing control becomes responsible for indicating the change itself:
public class SomeClass : PictureBox
{
public SomeClass()
{
this.BackColor = Color.Green;
this.MouseDown += new System.Windows.Forms.MouseEventHandler(mouseDown);
}
public void mouseDown(object sender, MouseEventArgs e)
{
OnFocussed(e);
}
public event EventHandler Focussed;
protected virtual void OnFocussed(EventArgs e)
{
EventHandler eh = Focussed;
if (eh != null)
{
eh(this, e);
}
}
}
And your Panel handles it:
{
MyPictureBox1.Focussed += MyPictureBox_Focussed;
MyPictureBox2.Focussed += MyPictureBox_Focussed;
MyPictureBox3.Focussed += MyPictureBox_Focussed;
}
void MyPictureBox_Focussed(object sender, EventArgs e)
{
SomeClass sc = sender as SomeClass;
if (sc != null)
{
BackColor = Color.Red;
}
}
Bad command or file name. Bad, bad command! Sit! Stay! Staaaay...
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
If I interpret your description literally, there;s a trivial solution that requires no exposure of your subclassed PictureBox to its Form context:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace CPWorking
{
public partial class PBX : PictureBox
{
public PBX()
{
InitializeComponent();
this.BackColor = Color.Green;
this.MouseDown += OnMouseDown;
}
private void OnMouseDown(object sender, MouseEventArgs mouseEventArgs)
{
this.Parent.BackColor = Color.Red;
}
}
} This exploits the fact that the Parent of a Control is a Control.
But, this is definitely not elegant, and probably not what you are looking for, so: let's make it more interesting, more like what you might do in a "real" app.
Like the approach OriginalGriff showed you, I will use a "broadcast" model, but I will implement it in a way that, imho, is more modern (uses a newer .NET facility), and, imho, more clearly expresses intent when viewed by newcomers to WinForms:
public partial class PBX : PictureBox
{
public PBX()
{
InitializeComponent();
this.MouseDown += OnMouseDown;
}
public Action<string, Color> PBXOnColorChanged { set; get; }
private void OnMouseDown(object sender, MouseEventArgs mouseEventArgs)
{
if (PBXOnColorChanged != null) PBXOnColorChanged((sender as PBX).Name, this.BackColor);
}
} Instead of using the "classic" syntax for a Delegate/EventHandler, this uses the newer syntax of the Action structure; do keep in mind that an Action is a Delegate. In this example we handle/respond-to the Action being invoked in the Form host context:
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.PBXOnColorChanged += PbxOnColorChanged;
pictureBox2.PBXOnColorChanged += PbxOnColorChanged;
pictureBox3.PBXOnColorChanged += PbxOnColorChanged;
}
private void PbxOnColorChanged(string pbxName, Color pbxBackColor)
{
} Yes, you could set the Panel BackColor to Red, if that's your thing
But, let's make this more like a real-world app by making the Form context not just get a one-way message, but, also send back a result to the sub-classed PictureBox that will determine what happens in the MouseDown event of the PictureBox:
public partial class PBX : PictureBox
{
public PBX()
{
InitializeComponent();
this.BackColor = Color.Green;
this.MouseDown += OnMouseDown;
}
public Func<string, Color, Color> PBXOnColorChanged { set; get; }
private void OnMouseDown(object sender, MouseEventArgs mouseEventArgs)
{
if (PBXOnColorChanged != null)
{
Color result = PBXOnColorChanged((sender as PBX).Name, this.BackColor);
if (result != this.BackColor && result != Color.Empty)
{
this.BackColor = result;
}
else
{
this.BackColor = Color.Green;
}
}
}
} Here we use the 'Func syntax that specifies the Type of its return value/object as well as its parameter Types.
In the Form context:
private Color PbxOnColorChanged(string pbxName, Color pbxBackColor)
{
Color NextColor = Color.Empty;
switch (pbxName)
{
case "pictureBox1":
if (pbxBackColor == Color.Green) NextColor = Color.Yellow;
break;
case "pictureBox2":
if (pbxBackColor == Color.Green) NextColor = Color.Red;
break;
case "pictureBox3":
break;
}
return NextColor;
} Note the event handler returns a Color value. Note that PictureBox3 wiii not change its Color when clicked because the event handler returns Color.Empty when it is clicked.
Elegant ? I don't think so ... trivial, really, but you can see some hopefully useful ideas in this example:
1. encapsulation, loose coupling: the sub-classed PictureBox has no access to any of its context's (Form) objects, properties, fields, etc.; it publicly exposes only one access point, a kind of "socket" (Action, or Func) that consumers of the object can "inject" a reference to executable code into at run-time.
2. the Form context, by design, limits its access to the instances of thr PictureBox it uses to a very structured message passing facility. It defines the PictureBox instances it creates as private.
3. the newer Action and Func language features make the task of implementing Delegates and EventHandlers much easier, an d, imho, easier to maintain. About the only limitation I am aware of compared to Delegates is that Delegate can use the 'params feature for passing a variable length Array of arguments; Action and Func cannot.
4. structured message passing is a useful model for dynamic exchange of data and implementation of object interaction.
«While I complain of being able to see only a shadow of the past, I may be insensitive to reality as it is now, since I'm not at a stage of development where I'm capable of seeing it.» Claude Levi-Strauss (Tristes Tropiques, 1955)
|
|
|
|
|
Message Closed
modified 27-Oct-17 11:10am.
|
|
|
|
|
Peter Vegter wrote: Is there a reason you don't just subscribe to the 'original' event (MouseDown)? Hi Peter, a very good question to ask.
I can't speak for OG's intent, but, note that in my code example the only thing that the sub-classed PictureBox exposes to the "outside" is a property of Type 'Action. In OG's code the MouseDown event handler is public, and the 'event delegate is public. Is that a significant difference: I'm not sure.
My code injects a reference to executable code (a "callback," if you will) into the sub-classed PictureBox: imho, that achieves minimal dependency. I believe "injection," and use of 'Action, and 'Func, are useful in educating C# students in a way that moves them toward the kind of thinking that, hopefully, will help them get to SOLID; my experience teaching this usage has reinforced that conviction.
Some students have a very difficult time grokking C#/,NET's Delegate/EventHandler syntax where you first have to create a Type declaration, the Delegate, then create an instance of that Type, then create an On-EventHandler/Event with its required null-check.
I welcome your feedback.
cheers, Bill
«While I complain of being able to see only a shadow of the past, I may be insensitive to reality as it is now, since I'm not at a stage of development where I'm capable of seeing it. A few hundred years later another traveler despairing as myself, may mourn the disappearance of what I may have seen, but failed to see.» Claude Levi-Strauss (Tristes Tropiques, 1955)
|
|
|
|
|
Message Closed
modified 27-Oct-17 11:08am.
|
|
|
|
|
Bill Woodruff has given you you excellent suggestions. I would change one detail. Instead of using MouseDown for your event, use MouseUp. A lot of events can happen in between and MouseUp is the last event in the chain. It can make debugging easier.
|
|
|
|
|
I started to use .NET Standard in earnest recently. And also updated them to lastest .csproj format (the one with almost nothing inside! ) and quite often what happens is, when I "try to compile" the compilation of my .NET Standard project (new .csproj style) just hang there and does nothing...
I only waited half an hour for my longuest test (playing game at home while waiting on a project with only 3 files in it) and.. nothing ever happens...
Any idea?
[EDIT]
It just happened to me right now and I had to delete BOTH the .vs and package folder to make it work.
|
|
|
|
|
I have been using VS2017 for over a year and never seen this. I suspect something is wrong with your installation.
|
|
|
|
|
yes but.. are you using the new .csproj format? looks like that:
(the whole .csproj file!)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.Net.Sockets" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="System.Interactive.Async" Version="3.1.1" />
<PackageReference Include="System.Reactive.Core" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Galador.Reflection\Galador.Reflection.PCL\Galador.Reflection.PCL.csproj" />
</ItemGroup>
</Project>
Also.. I have to admit to edit the .csproj (to convert from old to new format), but it was already 3 days ago...
|
|
|
|
|
Ah, I see. No, mine are still old format.
|
|
|
|
|
Super Lloyd wrote: And also updated them to lastest .csproj format
Updated what exactly?
I just you start with a brand new project. Don't update anything. Do a hello world program. If that doesn't work then something is wrong with your platform (hardware, OS, other apps and/or install.)
If it works, then presuming that you have some existing code, add that in to the new project above. If this fails then I would expect same problem as above.
If that works then I would suppose the problem is with how you "updated" the project file.
|
|
|
|
|
Hi. I want the MainForm to start minimized and inactive - no focus on it and its taskbar button. I tried many solutions (including the one below) but nothing worked: the form still gets focus when it's started. I'm using Windows 10. Also the TopMost property is off.
Any ideas how to make it work?
protected override bool ShowWithoutActivation
{
get
{
return true;
}
}
const int WS_EX_NOACTIVATE = 0x08000000;
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
createParams.ExStyle |= WS_EX_NOACTIVATE;
return createParams;
}
}
modified 22-Oct-17 10:21am.
|
|
|
|
|
Keep in mind that the TaskBar Button "belongs to" Windows, not your WinForms App: it triggers standard window size/activation functions that your WF App has no way to cancel.
While you can do "weird stuff," like setting the Main Form Location property so it's off-screen, imho, you should have a compelling reason for changing such standard behavior ... behavior Users learn to expect. Of course, your WinForms App can infer the effects of those Win behaviors by handling the Form's events like 'Activated and 'SizeChanged.
There is a technique, for taking full control of showing a series of Forms, without direct Win API invocations, which I'll be happy to show you an example of ... but, first ... you ...
1. confirm this is Windows Forms
2. describe what you want to do here in more detail: more than one Form ? What id each Form's purpose ?
«While I complain of being able to see only a shadow of the past, I may be insensitive to reality as it is now, since I'm not at a stage of development where I'm capable of seeing it. A few hundred years later another traveler despairing as myself, may mourn the disappearance of what I may have seen, but failed to see.» Claude Levi-Strauss (Tristes Tropiques, 1955)
modified 21-Oct-17 6:56am.
|
|
|
|
|
Simplest solution? For winForms, just set the main Form.WindowState to FormWindowState.Minimized
Bad command or file name. Bad, bad command! Sit! Stay! Staaaay...
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
Hi, OG, I think, but am not sure, the OP is looking to show another Form before the Main Form ... perhaps a log-in Form, or a splash-screen ... without activating the Main Form. That can be done using a custom ApplicationContext.
Hopefully, the OP will clarify what he's doing
cheers, Bill
«While I complain of being able to see only a shadow of the past, I may be insensitive to reality as it is now, since I'm not at a stage of development where I'm capable of seeing it. A few hundred years later another traveler despairing as myself, may mourn the disappearance of what I may have seen, but failed to see.» Claude Levi-Strauss (Tristes Tropiques, 1955)
|
|
|
|
|
Could be - difficult to tell with some of these.
How are you doing? Had your eyes fixed yet?
Bad command or file name. Bad, bad command! Sit! Stay! Staaaay...
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
What I'm trying to do: I start my app from another app (XnView). My app gets a list of files from XnView. I want my app to start minimized and to not steal the focus from XnView.
|
|
|
|
|
I did that too. The form starts minimized but it still steals the focus from the current app.
|
|
|
|
|
|
I've seen that. Actually the code that I posted is from that site. Anyone could make a working test solution with just an empty form that is not activated when it's launched and send it to me?
|
|
|
|
|
The "override ShowWithoutActivation"-method works for me - on Windows 7.
For testing I made two projects, one Forms-Project, one Console-Project. The Console-Project is basically mimicking XnView starting your app.
Essentials of the Forms-Project:
static class Program
{
[STAThread]
static void Main(string[] args)
{
bool withoutActivation = false;
if (args.Length > 0 && !String.IsNullOrWhiteSpace(args[0]))
withoutActivation = args[0] == "1";
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(withoutActivation));
}
}
public partial class Form1 : Form
{
private bool WithoutActivation;
public Form1(bool withoutActivation)
{
WithoutActivation = withoutActivation;
InitializeComponent();
label1.Text = WithoutActivation ? "without activation" : "with activation";
}
protected override bool ShowWithoutActivation
{
get
{
return WithoutActivation;
}
}
}
Essentials of the Console-Project:
class Program
{
static void Main(string[] args)
{
bool withoutActivation = false;
while (!Console.KeyAvailable)
{
Thread.Sleep(3000);
Process.Start("[path to forms-app.exe goes here]", withoutActivation ? "1" : "0");
withoutActivation = !withoutActivation;
}
}
}
Every second "invocation" of the forms-app steals the focus from the currently active app, every other one does not.
Edit: Just realized that I didn't do the "start minimized" part (it's late here..). When I do show it minimized (by inserting WindowState = FormWindowState.Minimized; after InitializeComponent(); ) then neither "invocation" steals the focus, regardless whether WithoutActivation is true or false. So either Windows 10 behaves differently there or you're doing something not quite right.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
|
|
|
|
|
Thanks for help. The test is working. But I'm a beginner and I have troubles implementing this on my actual project. I'm using SingleInstanceController at Program.cs in order to prevent opening the same app twice (forced single instance). Because of that, the simple ShowWithoutActivation doesn't work.
This app works in 2 main stages:
1. The app starts minimized, then it reads the arguments (file paths), then it displays those arguments in a listbox, then it waits.
2. If it's started again, it gets other arguments (actually just one), rename the files from stage 1, then it quits.
Basically it's a file (picture) arranger: the user selects the files to be renamed (stage 1) and the target file after those files will be placed through auto-renaming (stage 2). Like this: FileArranger - Streamable[^] Each stage means a launch of this app.
I want this app to stay minimized and without focus during its whole "lifetime". Of course, except when the user activates it by clicking its taskbar button. I uploaded the whole project here: MEGA[^] This app has around 300 LOC (MainForm.cs + Program.cs). Please, could you modify it in order to start without focus? That would be very helpful.
modified 21-Oct-17 21:37pm.
|
|
|
|
|
You using WindowsFormsApplicationBase and its event StartupNextInstance to implement a "SingleInstanceController" is the important piece of information here.
The code in WindowsFormsApplicationBase that is calling your event handler ThisStartupNextInstance (which I would suggest renaming to something like "StartedAgain") is bringing your MainForm to the foreground. Unless you explicitly tell it not to by inserting this line into the event handler method:
e.BringToForeground = false; (Take a look at the tooltip of BringToForeground .)
I assume the line form.Hide(); there was an attempt to solve your problem; you probably want to remove it.
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson
modified 22-Oct-17 3:11am.
|
|
|
|
|