I don't know why you need static controls. Maybe you know something I don't. I've used a lot of visual inheritance but I've never seen static controls to be necessary. If you have a common treeview control, let every form instance have its own instance of the control, and share a single instance of the data bound to the treeviews.
Sharing control state (as opposed to data) between forms is also an unusual requirement. Are you sure FormB really needs to know about the state of buttons on FormA?Consider the MVP or MVC designs. Think of each form as a dumb "view" that knows nothing about the other views or even the application itself. Supervise each view with a smart presenter/controller. If it makes sense, a single presenter can supervise several views. Associate a state object with each view. If you have some some state which needs to be shared between views, let the presenter(s) mediate this (and consider databinding - see below).
Agreed, Visual Studio will give you headaches. When considering form or usercontrol inheritance, you need to carefully weigh the benefits against the potential (and probable) cost of wrestling with the form designer's frustrating quirks and limitations. I suggest keeping form inheritance to a minimum - use it only when the payoff is high. Keep in mind that, as an alternative to subclassing, you can create common "base" form and simply instantiate it once for each would-be "child" and then customize it on-the-fly. This makes sense when differences between each version of the form are minor compared to the shared aspects. (IOW: complex base form, only-slighty-more-complex child forms)
Do make use of usercontrols when it helps you prevent significant duplication of UI development. Consider usercontrol inheritance but apply the same considerations as for form inheritance.
I think the most important advice I can offer is, if you don't currently employ some form of the view/controller pattern, I strongly encourage you to start doing so. It forces you to learn and appreciate the benefits of loose-couping and layer separation.
response to your update
Which layer should store stuff like, the last pressed button, so that I can keep it highlighted for the user...
You can share state between views much like you would share state between a presenter and its view. Create a special class SharedViewState. For simplicity you can make it a singleton, or you can instantiate it in the main presenter and pass it to all views (via their presenters) from there. When the state is associated with controls, use data binding where possible. Most Control
properties can be data-bound. For example the BackColor property of a Button could be bound to a property of your SharedViewState class. If you do this binding on all forms which have identical buttons, you can highlight Button1 on all forms just by setting SharedViewState.Button1BackColor = someColor
.
If you aren't familiar with WinForms databinding, hit MSDN and do some reading. It's not difficult. Learn about INotifyPropertyChanged
and you're halfway there.
Here's a typical implementation of a viewstate class with the Button1BackColor property as an example:
public class SharedViewState : INotifyPropertyChanged
{
// boilerplate INotifyPropertyChanged stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// example of a property for data-binding
private Color button1BackColor;
public Color Button1BackColor
{
get { return button1BackColor; }
set
{
if (value != button1BackColor)
{
button1BackColor = value;
NotifyPropertyChanged("Button1BackColor");
}
}
}
}
I think you may be getting a little bogged down in the English meaning of state, when compared to the State Pattern (or Finite-State Machine, which is really a diagrammatic representation of a State Pattern). Both are appropriate here, but they shouldn't be confused.
The State Pattern is something which should, given various common stimuli, operate on the game state differently at different times in the game. So, as you've rightly concluded, the lobby stage (which I'm assuming to be while people are joining a game) is a State in that context.
The game state is a list of players, pieces, cards, dice, whatever else, and all the information required to simulate them. You might want to call that the context.
Each State should receive a UserClicked message from the application which receives the context and coordinates or area clicked and should operate on those accordingly. The information that you are trying to give ownership of to the State object does not belong there, it belongs in the game itself, alongside the State.
The state may also have a UserPressedKey method, a TimerTicked method or any other kind of stimulus on which it should act. But each of these should act on the game state rather than being the game state.
One important method of the State will be ScreenRefresh, which will draw the context for the user.
Here is a very rough example:
abstract class ApplicationState {
void Begin(GameContext context);
void UserClicked(GameContext context, int x, int y);
void UserPressedKey(GameContext context, char key);
void TimerTicked(GameContext context);
void ScreenRefresh(GameContext context);
protected void OnStateChanged(ApplicationState newState) {
// inform interested parties that state has changed,
// using the observer pattern
}
}
class LobbyState : ApplicationState
{
void Begin(GameContext context) {
context.Players = new User[context.NoOfPlayers];
}
void UserClicked(GameContext context, int x, int y) {
// Find a displayed player box which contains click coords
int index = -1;
for (i=0; i < context.Players.Length; i++) {
if (PlayerBox[i].Contains(x, y)) {
index = i;
}
}
if (index > -1) {
if (context.Player[index] == null) {
context.Player[index] = context.ActivePlayer;
} else if (context.Player[index] == context.ActivePlayer()) {
context.Player[index] = null;
} else {
ErrorSound.Play();
}
} else if (CloseIcon.Contains(x, y)) {
OnStateChanged(new ExittingState());
} else {
ErrorSound.Play();
}
}
void UserPressedKey(GameContext context, char key) {
if (key = 'J' or key = 'j') {
if (! context.AddPlayerRandomly(context.ActiveUser)) {
ErrorSound.Play();
}
if (context.GameIsNowFull()) {
OnStateChanged(new InitializingState());
}
} else if key = Esc {
if (!context.RemovePlayer(context.ActiveUser)) {
ErrorSound.Play();
}
} else {
ErrorSound.Play();
}
}
void TimerTicked(GameContext context) {
// there is no use for a game timer while
// we're trying to fill the game, but you
// want one to tick anyway, because the
// application doesn't know which state
// it is in.
}
void ScreenRefresh(GameContext context) {
DrawPlayerBoxes(context.Screen, context.Players);
DrawExitIcon(context.Screen);
}
}
class InitializingState : ApplicationState {
private double waitSpinnerAngle = 0;
void Begin(GameContext context) {
context.RandomizePlayOrder();
context.ShuffleCards();
// all other game initialization rules here
OnContextChanged(new PlayerUpState(context.Players[0]));
}
void UserClicked(GameContext context, int x, int y) {
// Not responding to user input for a moment
ErrorSound.Play();
}
void UserPressedKey(GameContext context, char key) {
// Not responding to user input for a moment
ErrorSound.Play();
}
void TimerTicked(GameContext context) {
waitSpinnerAngle += 0.05;
if (waitSpinnerAngle > 1) waitSpinnerAngle = 0;
}
void ScreenRefresh(GameContext context) {
DrawWaitSpinner(waitSpinnerAngle);
}
}
See where I'm going with this? The Application itself is then as simple as setting the initial State, hooking a listener into the OnStateChanged event and calling Begin. When the listener hears that the event is called, unhook your old State, hook in the new one, and call Begin again.
Everything else is triggered by events from the mouse, keyboard or timer, passed directly to the current State without knowledge of which State the game is currently in. If you are running this game across a network then you will also need an event for changes of state received from other players.
Everything in your State and Context is now very unit-testable and separation of concerns are observed. Although, you may want to refactor that LobbyState#UserClicked method a bit, among other things.
Remember, this is just a free example and you get what you paid for it. Don't try to apply it directly to your game. Just use it to understand how the State Pattern should work in the context of a game.
Best Answer
Is it wrong? No. Will it cause people to complain about your code? No.
Will it sidetrack some busy developer for a couple of minutes while he tries to find out what the parameters are and whether they will help solve his problem? Could be..
Will it make your class easier to use...?
Standards and patterns are useful because they are easily recognized and used (or ignored). Deviating from conventions should be done for a purpose, not just because you can. This applies whether it is bracket style, parameter names or argument list. Do it for a reason, not just because it compiles.