I have changed my app.config file to allow the user to change the color scheme of the program. I can figure out how to change the background color of the form they are on where they change these settings:
Color colBackColor = Properties.Settings.Default.basicBackground;
this.BackColor = colBackColor;
But how can I change all of my forms background color? It's like I still want to pass all my forms to a function. I already asked that question and someone told me to use the app.config file. Now that I have done that, am I using it wrong?
It's simply that you need a base form from which all your forms in your project have to inherit:
public class FormBase : Form {
protected override void OnLoad(EventArgs e){
Color colBackColor = Properties.Settings.Default.basicBackground;
BackColor = colBackColor;
}
}
//Then all other forms have to inherit from that FormBase instead of the standard Form
public class Form1 : FormBase {
//...
}
public class Form2 : FormBase {
//...
}
UPDATE
public interface INotifyChangeStyle {
void ChangeStyle();
}
public class FormBase : Form, INotifyChangeStyle {
protected override void OnLoad(EventArgs e){
ChangeStyle();
}
public void ChangeStyle(){
//Perform style changing here
Color colBackColor = Properties.Settings.Default.basicBackground;
BackColor = colBackColor;
//--------
foreach(var c in Controls.OfType<INotifyChangeStyle>()){
c.ChangeStyle();
}
}
}
public class MyButton : Button, INotifyChangeStyle {
public void ChangeStyle(){
//Perform style changing here
//....
//--------
foreach(var c in Controls.OfType<INotifyChangeStyle>()){
c.ChangeStyle();
}
}
}
//... the same for other control classes
Related
I have classes called ButtonDesign, TextBoxDesign, and a few more. Everyone’s realization is exactly the same. I add a number of properties and functions to each control, the question is how can this be achieved without duplicate code, i.e .: is there a way to create only one class with the same attributes - and these attributes will be added to all the controls I want? .
If I create a primary class that inherits from the Control class - then these attributes will only be in the Control class and not in all the controls I want.
This is the code I'm trying to:
class ButtonDesign : Button
{
private Control save_properties = new Control();
public Color OnMouseHoverColor { get; set; }
public ButtonDesign()
{
this.MouseEnter += ButtonDesign_MouseEnter;
this.MouseLeave += ButtonDesign_MouseLeave;
}
private void ButtonDesign_MouseEnter(object sender, EventArgs e)
{
save_properties.BackColor = this.BackColor;
this.BackColor = this.OnMouseHvetColor;
}
private void ButtonDesign_MouseLeave(object sender, EventArgs e)
{
this.BackColor = save_properties.BackColor;
}
}
class TextBoxDesign : TextBox
{
private Control save_properties = new Control();
public Color OnMouseHoverColor { get; set; }
public TextBoxDesign()
{
this.MouseEnter += ButtonDesign_MouseEnter;
this.MouseLeave += ButtonDesign_MouseLeave;
}
private void TextBoxDesign_MouseEnter(object sender, EventArgs e)
{
save_properties.BackColor = this.BackColor;
this.BackColor = this.OnMouseHvetColor;
}
private void TextBoxDesign_MouseLeave(object sender, EventArgs e)
{
this.BackColor = save_properties.BackColor;
}
}
Congratulations, you've encountered the diamond inheritance problem. C# doesn't support this scenario, so what you're attempting to do is unfortunately not possible.
Your only options are:
Create a subclass of Control and make your custom controls inherit from it. This means you have to recreate the behaviour of the standard WinForms controls in your subclasses, which is painful at best.
public class MyControl : Control {}
public class MyButton : MyControl {}
Create an interface that your common controls implement. Have the interface implementations delegate to a shared library that does what needs to be done:
public interface IMyControl
{
void MySharedOperation();
}
public class MyButton : Button, IMyControl
{
public void MySharedOperation()
=> MySharedOperationHandler.MySharedOperation(this);
}
public static class MySharedOperationHandler
{
public static void MySharedOperation(Control control) {}
}
You'll end up with a fair amount of method implementations that do nothing more than delegate, but IMO this is far better than reinventing the control wheel as in the previous option.
If you want this, your class ButtonDesign is not a Button, it has a Button and a Layout. Similarly your class TextBoxDesign has a TextBox and a Layout.
In other words: don't use inheritance, use aggregation!
Every property that are both in Buttons, TextBoxes, and other Controls that have the properties that you want to change for all items in one statement, create a class that contains all these properties, with the proper events.
For every property you need a private member, a public get/set property and an event that will be raised when the property changes. Something like this:
class Layout
{
private Color backColor; // TODO: give proper initial values
private Color foreColor;
... // other properties that you want to change for all Controls
// Events:
public event EventHander BackColorChanged;
public event EventHandler ForeColorChanged;
... // etc
// Event raisers:
protected virtual void OnBackColorChanged()
{
this.BackColorChanged?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnForColorChanged()
{
this.ForeColorChanged?.Invoke(this, EventArgs.Empty);
}
...
// Properties:
public Color BackColor
{
get => this.backColor;
set => if (this.BackColor != value) this.OnBackColorChanged();
}
public Color ForeColor ...
public Size Size ...
}
Your DesignButton will be a UserControl that has a Docked Button and a Layout. The constructor subscribes to the events. When raise the corresponding property on the button is set.
Use the visual studio designer to create the UserControl. Code will be similar to the following:
class MyButton : UserControl
{
private Button button; // visual studio designer will create this
private Layout layout;
public Layout Layout
{
get => this.layout;
set => if (this.Layout != value) this.ChangeLayout;
}
// you can't set the design properties. Only get.
protected Button Button => this.button;
Color BackColor
{
public get => this.Button.BackColor;
private set => this.Button.BackColor = value;
}
// etc for ForeColor, Text, ...
protected virtual void ChangeLayout(Layout newLayout)
{
// TODO: if there is an old Layout: desubscribe from all events from old Layout
this.layout = newLayout;
// subscribe to all events:
this.layout.BackColorChanged += this.BackColor_Changed;
this.layout.forColorChanged += this.ForColor_Changed;
...
this.BackColor = this.BackColor;
// TODO: if desired, for completeness add an event: LayoutChanged
}
Now whenever Layout raises event BackColorChanged you handle this event and assign the value to the Button. You'll get the gist.
Do something similar for TextBoxes, ComboBoxes, etc
Usage:
Layout commonLayout = new Layout
{
BackColor = Color.Yellow,
ForeColor = color.Black,
...
};
MyButton button1 = new MyButton
{
Layout = commonLayout,
};
MyTextBox textBox1 = new MyTextBox
{
Layout = commonLayout,
}
MyComboBox comboBox1 = ...
// Change the backgroundColor for all items:
commonLayout.BackColor = Color.Red;
If you have a lot of properties, consider to use generic classes
class LayoutProperty<T>
{
private T propertyValue;
public event eventHandler PropertyValueChanged
protected void OnPropertyValueChanged()
{
this.PropertyValueChanged?.Invoke(this, EventArgs.Empty);
}
public T PropertyValue
{
get => this.propertyValue;
set => if (this.PropertyValue != value) this.OnPropertyValueChanged();
}
}
class Layout
{
private PropertyValue<Color> backColor;
private PropertyValue<Color> foreColor;
// etc, see above for subscribtion and raising events.
And for all Controls:
public MyControl
{
private Control control;
private Layout layout;
// etc, see above for the event handling.
}
public MyButton : MyControl {Control = new Button()}
public MyTextBox : MyControl {Control = new Textbox()}
Inside my public partial class frmMain : Form I have private class Tile.
Inside the class Tile I have private PictureBox pic = new PictureBox();.
I have registered a click event for those pictureBoxes (inside the Tile class):
public void Initialize()
{
pic.Click += new EventHandler(swap);
}
When I click a pictureBox I want to be able to see textbox1.Text.
private void swap(object sender, EventArgs e)
{
// code here //
if (won)
{
MessageBox.Show(textBox1.Text);
}
}
How can I make textbox1.Text and some other fields of my frmMain instance visible?
The quickest solution would be to pass frmMain as parameter to Tile class. Tile class would be:
private class Tile {
private frmMain frm;
//constructor
public Tile(frmMain frm) {
this.frm = frm;
}
... your code ...
//now you can
private void swap(object sender, EventArgs e)
{
if (won)
{
MessageBox.Show(frm.textBox1.Text);
}
}
}
Remember that the textBox1 have to be public accessible from frmMain.
The cleanest solution would be to expose interface from frmMain and pass it as parameter to Tile class.
Interface
public interface IfrmMain_GetText
{
string gettextBox1Text();
}
frmMain
public partial class frmMain : Form, IfrmMain_GetText
{
... your code ...
public string gettextBox1Text(){
return textBox1.Text;
}
}
Tile class
private class Tile {
private IfrmMain_GetText frmInterface;
//constructor
public Tile(IfrmMain_GetText frmInterface) {
this.frmInterface = frmInterface;
}
... your code ...
//now you can
private void swap(object sender, EventArgs e)
{
if (won)
{
MessageBox.Show(frmInterface.gettextBox1Text());
}
}
}
How can I make textbox1.Text and some other fields of my frmMain instance visible?
In C# windows forms, controls are private by default. To change the access modifier, you can change it like below:
Right click the control > Properties > Change dropdown as shown below
I would change it to internal, no need to make it public in your case.
That means classes outside the class where the control is (frmMain) can access the control. But your inner class will need a reference to frmMain so it can access the controls of frmMain. To do that, you can do it in the following ways:
Option 1
You can pass the frmMain reference to a class in the constructor:
public class SomeClass
{
private frmMain someForm;
public SomeClass(frmMain someForm)
{
this.frmMain = someForm;
// Now you can do this
var ctrl = this.frmMain.WhateverControlYouNeedToAccess;
string controlText = ctrl.Text; //assuming it has Text property
}
}
During creation of the SomeClass, pass your form to it:
// this reference to the current instance
// this code will be in your form
SomeClass some = new SomeClass(this);
Option 2
In your class have a property so it is settable from outside like this:
public class SomeClass
{
public frmMain SomeForm { get; set; }
}
You will set the property like this:
SomeClass some = new SomeClass();
some.SomeForm = this;
Option 3
Only give the class the minimum it needs. You do not need to give it a reference to the whole form but only one control (or more), then pass the control specifically like this:
SomeClass some = new SomeClass();
some.PictureBoxA = this.pictureBox1;
some.Button1 = this.button1;
For this to work, your class needs to have the properties for the above. So your class will have properties like this:
public class SomeClass
{
public PictureBox PictureBoxA { get; set; }
public Button Button1 { get; set; }
}
You need to pass a form-reference to the nested class. You can do so through constructor.
private class Tile
{
private frmMain _frm;
public Tile (frmMain frm)
{
_frm = frm;
}
private void swap(object sender, EventArgs e)
{
if (won) {
MessageBox.Show(_frm.textBox1.Text);
}
}
}
You either need to make textBox1 public or to encapsulate it in a public property.
A cleaner way is to hold the information in a data class and to use object binding to bind its properties to the textboxes. Then you can pass this data object to the Tile class.
See: A Detailed Data Binding Tutorial on CodeProject.
I don't know what kind of data is displayed on your form, but if tile data is displayed, then the Tile class could act as data class and you could bind the tile object to your form.
I'm building custom control by extending ScrollableControl.
Problem is that my custom control acts as container - I can drag controls into it:
My question is how can I disable container functionality in class that extends ScrollableControl
Below are two test controls, one extends Control, second ScrollableControl
public class ControlBasedControl : Control
{
protected override Size DefaultSize
{
get { return new Size(100, 100); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LightCoral, ClientRectangle);
}
}
public class ScrollableControlBasedControl : ScrollableControl
{
public ScrollableControlBasedControl()
{
AutoScrollMinSize = new Size(200, 200);
}
protected override Size DefaultSize
{
get { return new Size(100, 100); }
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillRectangle(Brushes.LawnGreen, ClientRectangle);
}
}
You get "acts-like-a-container" behavior at design time from the [Designer] attribute. Copy-pasting from the Reference Source:
[
ComVisible(true),
ClassInterface(ClassInterfaceType.AutoDispatch),
Designer("System.Windows.Forms.Design.ScrollableControlDesigner, " + AssemblyRef.SystemDesign)
]
public class ScrollableControl : Control, IArrangedElement {
// etc...
}
It is ScrollableControlDesigner that gets the job done. Doesn't do much by itself, but derived from ParentControlDesigner, the designer that permits a control to act as a parent for child controls and gives it container-like behavior at design time.
Fix is easy, you just have to use your own [Designer] attribute to select another designer. Add a reference to System.Design and make it look like this:
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms.Design; // Add reference to System.Design
[Designer(typeof(ControlDesigner))]
public class ScrollableControlBasedControl : ScrollableControl {
// etc...
}
There is probably more than one way to accomplish this, but here is what I would do...
First create a read-only version of ControlCollection
public class ReadOnlyControlCollection : Control.ControlCollection
{
public ReadOnlyControlCollection(Control owner)
: base(owner)
{
}
public override bool IsReadOnly
{
get { return true; }
}
public override void Add(Control control)
{
throw new ArgumentException("control");
}
}
Then make your ScrollableControlBasedControl create an instance of ReadOnlyControlCollection in stead of the default ControlCollection
public class ScrollableControlBasedControl : ScrollableControl
{
protected override Control.ControlCollection CreateControlsInstance()
{
return new ReadOnlyControlCollection(this);
}
// The rest of your class goes here...
}
I use Visual Studio 2010 and when I drop a control on an ScrollableControlBasedControl the control is magically moved back to where it came from, as if the action was cancelled.
In my application I have got an option to customise display a bit. Basically, now it is about changing buttons' and pictureboxes' images, changing form icons etc. As you can see in the code I provided bellow. So, in this case, I have done this by simply loading a bunch of resources (images) and then when I change theme:
if (Properties.Settings.Default.Theme == "Purple")
{
foreach (var form in Application.OpenForms.Cast<Form>())
{
form.Icon = Properties.Resources.Purple;
}
Main f1 = (Main)Application.OpenForms["Main"];
Settings f2 = (Settings)Application.OpenForms["Settings"];
f1.btn_Exit.Image = Properties.Resources.EXIT_purple;
f2.btn_SaveSettings.Image = Properties.Resources.SaveSettings_purple;
f1.pictureBox1.Image = Properties.Resources.Preview_purple;
}
This dramatically increases the size of the application, so I came up with changing BackColor instead of loading another images. I have tried to solve this in the following way:
if (Properties.Settings.Default.Theme == "Purple")
{
foreach (var form in Application.OpenForms.Cast<Form>())
{
form.Icon = Properties.Resources.Purple;
}
Main f1 = (Main)Application.OpenForms["Main"];
Settings f2 = (Settings)Application.OpenForms["Settings"];
f1.btn_Exit.BackColor = Color.FromArgb(164, 57, 226);
f2.btn_SaveSettings.BackColor = Color.FromArgb(164, 57, 226);
f2.pictureBox1.BackColor = Color.FromArgb(164, 57, 226);
}
So, form icon should be changed on all running forms and I have managed to successfully do so, but when it comes to buttons and pictureboxes, I am not seeing any results, that is buttons and pictureboxes are simply not changing. Besides form icons, I should change btn_Exit located on the Main form, btn_SaveSettings located on the Settings form, and pictureBox1 located on the Settings form as well.
How can I solve this issue?
If it was me doing it I would make an interface. This interface would have a reference to the correct picture/icon/color for your different controls. The Settings for you application would hold an instance of the correct theme. Each form I would OnLoad update all the necessary items and call it a day. It's probably not the most elegant solution, but it would make it easy and quick when you go to add more themes.
This is what I was thinking about. I hope this clears up my approach.
public class Settings
{
public static ITheme Theme {get {return theme;}{set theme = value}}
theme = new DefaultTheme();
}
public interface ITheme
{
public Color BackgroundColor {get;}
public Color ButtonBackgroundColor { get;}
//... etc
}
public class DefaultTheme : ITheme
{
public Color BackgroundColor {get{ return Color.White;}}
public Color ButtonBackgroundColor { get { return Color.Gray;}}
}
from here you have 2 approaches. Both require you to put a Event "OnLoad" If you have 1 or 2 forms maybe put this code each Form (not recommended) or you could use an extension like method.
here is first approach. put the code directly in each frame.
public class SaveFrame : Frame
{
public SaveFrame()
{
InitializeComponents();
}
public Form_OnLoad()
{
var theme = Settings.Theme;
this.Background = theme.BackgroundColor;
foreach(Button b in this.Controls)
{
if(b != null)
b.Background = theme.ButtonBackgroundColor;
}
}
}
or create an extension method. and put it in the OnLoad
public static class FormExtensions
{
public static void UpdateTheme(this Form form, ITheme theme)
{
form.Background = theme.BackgroundColor;
foreach(Button b in form.Controls)
{
if(b != null)
b.Background = theme.ButtonBackgroundColor;
}
}
}
public class SaveFrame : Form
{
public SaveFrame()
{
InitializeComponents();
}
public Form_OnLoad()
{
this.UpdateTheme(Settings.Theme);
}
}
public class MainFrame :Form
{
public MainFrame()
{
InitializeComponents();
}
public Form_OnLoad()
{
this.UpdateTheme(Settings.Theme);
}
}
I have a little problem with windows forms in c#.
Let's keep it simple: I have a method which sets the default background color and foreground color. I have multiple forms from which I want to call it and I want have only one method (keep the possibility to add a default background image, etc... ). How should I do it ?
This is the basic code:
public void LoadGraphics() {
this.BackColor = Graphics.GraphicsSettings.Default.BackgroundColor;
this.ForeColor = Graphics.GraphicsSettings.Default.ForegroundColor;
this.BackgroundImage = new Bitmap(Graphics.GraphicsResources.bg_small);
}
Create a parent class that implements the method and derive your Forms from that parent class:
class Foo : Form {
void LoadGraphics() {
this.BackColor = Graphics.GraphicsSettings.Default.BackgroundColor;
this.ForeColor = Graphics.GraphicsSettings.Default.ForegroundColor;
this.BackgroundImage = new Bitmap(Graphics.GraphicsResources.bg_small);
}
}
class YourForm : Foo {
void someFunction() {
LoadGraphics();
}
}
You can create a static class that contains the code to be shared between your forms:
static class Utils
{
public static void ChangeColor(Form form, Color color)
{
form.BackColor = color;
}
}
Then you can call this function from any other form:
Utils.ChangeColor(this, Color.Red);