I developed a composite control using C# Windows Forms Control Library and code as follow:
public partial class MyControl : UserControl
{
public event EventHandler NameChanged;
protected virtual void OnNameChanged()
{
EventHandler handler = NameChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
private void WhenNameChanged(object sender , EventArgs e)
{
this.myGroupBox.Text = this.Name;
}
protected override void OnCreateControl()
{
base.OnCreateControl();
IComponentChangeService changeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
if (changeService == null) return; // not provided at runtime, only design mode
changeService.ComponentChanged -= OnComponentChanged; // to avoid multiple subscriptions
changeService.ComponentChanged += OnComponentChanged;
}
private void OnComponentChanged(object sender, ComponentChangedEventArgs e)
{
if(e.Component == this && e.Member.Name == "Name")
{
OnNameChanged();
}
}
public MyControl()
{
InitializeComponent();
this.NameChanged += new EventHandler(this.WhenNameChanged);
}
}
The MyControl only has one GroupBox control named myGroupBox
private System.Windows.Forms.GroupBox myGroupBox;
I have a test program which is a C# Windows Forms Application, and I would like to see that when I change the name of myGroupBox in properties window, the text of myGroupBox will be the name I typed. so I typed a name Value to myGroupBox in the properties window, the text will change in designer, here is the screenshot:
But when I rebuild the test program or at run time the text of myGroupBox will disappear, here is the screenshot:
what should I do with my code? thanks
So the problem is that the Name of a Control (or UserControl) is empty after construction. The name given by you and stored in the resources will be set in OnLoad.
So a solution for you could be to override OnLoad with something like this:
public class MyControl : UserControl
{
// ... the code so far
// override OnLoad
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
myGroupBox.Text = Name; // or WhenNameChanged(this, EventArgs.Empty);
}
}
So the name taken from the resources (e.g. after rebuild/reopen designer) will be set here again and so also set as myGroupBox.Text.
Hope that helps.
Related
My window drag is not working.
here is my code :
public WindowsButtonsVm(Window currentWindow)
{
_currentWindow = currentWindow;
}
public void MoveWindow(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
try
{
// Begin dragging the window
_currentWindow.DragMove();
}
catch (Exception err)
{
}
}
Current window is my main window that I passed in.
my class WindowsButtonsVm inherits from from this class:
public class ViewModelBase : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
when the user does left mouse down on my header button is I called this :
private void HeaderBackgroundBtn_MouseDown(object sender, MouseButtonEventArgs e)
{
WindowsButtonsVm WindowBtn = (WindowsButtonsVm) WindowButtons1.DataContext;
WindowBtn.MoveWindow(e);
}
So I feel like I did everything right, but I am getting this error:
"Can only call DragMove when primary mouse button is down."
You must implement dragging in the code-behind of the particular Window. Never handle UI in the view model.
partial class MyWindow : Window
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// Begin dragging the window
DragMove();
}
}
Or to allow dragging the Window by clicking any child element, simply handle the tunneling event version:
partial class MyWindow : Window
{
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
// Begin dragging the window
DragMove();
}
}
I have 2 controls that inherit UserControl
one is a container and the other is a collection of basic text boxes lables etc. hereby labeled as ContainerControl and ComboControl.
ContainerControl contains a List<ComboControl> and a foreach loop that adds them to a FlowLayoutPanel. ComboControl has a button that I would like to be used to clear itself from its parent's List.
I am not sure what the best way of doing this would be. this.parent and cast to ContainerControl or would Dispose() work? I'm fairly sure I could pass a ref to the List, but that sounds needlessly messy...
public partial class ContainerControl : UserControl
{
List<ComboControl> ComboControls = new List<ComboControl>();
...
//procederaly generate and fill ComboControls here
...
foreach (ComboControl value in ComboControls)
{
this.flowLayoutTable.Controls.Add(value);
}
...
}
public partial class ComboControl : UserControl
{
private void BtnDel_Click(object sender, EventArgs e)
{
//what goes here
}
...
}
Along the lines of what Zohar Peled said, something like this to remove a control to avoid leaking resources.
private void cleanup(Control c)
{
foreach(Control child in c.Controls)
cleanup(child);
if (c.Parent != null)
{
c.Parent.Controls.Remove(c);
c.Dispose();
}
}
For scenario like this i would use a custom event to send a delete request to parent control :
your ComboControl with Custom Event :
//Create Custom Event
public delegate void DeleteControlDelegate(object sender);
public partial class ComboControl : UserControl
{
//Custom Event to send Delete request
public event DeleteControlDelegate DeleteControlDelegate;
public ComboControl()
{
InitializeComponent();
}
//Invoke Custom Event
private void OnDeleteControl(object sender)
{
DeleteControlDelegate?.Invoke(sender);
}
private void BtnDel_Click(object sender, EventArgs e)
{
//On ButtonClick send Delete request
OnDeleteControl(this);
}
}
and in your ContainerControl subscribe to event of each ComboControl :
List<ComboControl> _comboControls = new List<ComboControl>();
public ContainerControl()
{
InitializeComponent();
}
private void ContainerControl_Load(object sender, EventArgs e)
{
_comboControls.Add(new ComboControl());
_comboControls.Add(new ComboControl());
_comboControls.Add(new ComboControl());
foreach (ComboControl value in _comboControls)
{
flowLayoutPanel.Controls.Add(value);
//Subscribe to Custom Event here
value.DeleteControlDelegate += Value_DeleteControlDelegate;
}
}
private void Value_DeleteControlDelegate(object sender)
{
//When Raised Delete ComboControl
flowLayoutPanel.Controls.Remove((Control) sender);
}
}
In visual studio how do you access a control on a form hosting a user control? For example, when text changes in a text-box in a user control, I want text in another text-box in another user control to change. Both these user controls are hosted on the same form. Thanks in advance!
If you need different UI for data entry, I prefer to have 2 controls with different UI, but I will use a single data source for them and handle the scenario using data-binding.
If you bind both controls to a single data source, while you can have different UI, you have a single data and both controls data are sync.
The answer to your question:
You can define a property in each control which set Text of TextBox. Then you can handle TextChanged event of the TextBox and then find the other control and set the text property:
Control1
public partial class MyControl1 : UserControl
{
public MyControl1() { InitializeComponent(); }
public string TextBox1Text
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (Parent != null)
{
var control1 = Parent.Controls.OfType<MyControl2>().FirstOrDefault();
if (control1 != null && control1.TextBox1Text != this.textBox1.Text)
control1.TextBox1Text = this.textBox1.Text;
}
}
}
Control2
public partial class MyControl2 : UserControl
{
public MyControl2() { InitializeComponent(); }
public string TextBox1Text
{
get { return this.textBox1.Text; }
set { this.textBox1.Text = value; }
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (Parent != null)
{
var control1 = Parent.Controls.OfType<MyControl1>().FirstOrDefault();
if (control1 != null)
control1.TextBox1Text = this.textBox1.Text;
}
}
}
Using c# and winforms with .NET 4.5 I would like to link the Checked property of a menu item with the Visible property of a form.
Changing any of these two attribute would change the other one to keep them synchronized.
Is there an easy and elegant solution to do that ?
Something like this example with a checkbox and a button:
Wire up to the CheckedChanged event
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
var checkBox = sender as CheckBox;
button1.Visible = !checkBox.Checked;
}
Edit:
Ok, i misunderstood.
Although the solution of 'farid' is a clean solution with separation of concerns using viewmodel and model it also adds more complexity to your application.
If you don't want to use this mvvm pattern and put the logic in the code behind you can implement the INotifyPropertyChanged interface to the form that has the visible property (or add a custom event), add a new Visible property that sets the base.visible property (inherited by the Form from the Control class) and raise the PropertyChanged event. in the form that contains the menu item you can wire up to the event and perform the necessary logic to set the checked state or do some other action.
Here is an example:
Form1 code behind:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
}
private Form2 _frm2;
private void Form1_Load(object sender, EventArgs e)
{
_frm2 = new Form2();
_frm2.MdiParent = this;
_frm2.PropertyChanged += _frm2_PropertyChanged;
_frm2.Show();
}
void _frm2_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Visible")
{
showToolStripMenuItem.Checked = _frm2.Visible;
}
}
private void showToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
{
var menuItem = sender as ToolStripMenuItem;
if (_frm2 != null)
_frm2.Visible = menuItem.Checked;
}
}
Form2 code behind:
public partial class Form2 : Form, INotifyPropertyChanged
{
public Form2()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
public new bool Visible
{
get
{
return base.Visible;
}
set
{
base.Visible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
}
private void hideButton_Click(object sender, EventArgs e)
{
Visible = false;
}
}
You can use Windows Forms Data Bindings.
Every windows forms control has a DataBindings property that can be used to bind a property in given data source to one of controls properties.
You can structure your code like example below:
This example shows binding from a ViewModel object to a control property. In your specific case you can bind a ViewModel property to two control property.
public partial class StackOverflowForm : Form
{
public ViewModel Model { get; set; }
public Dictionary<string, Control> BindableControls { get; set; }
public StackOverflowForm()
{
Model = new ViewModel();
Model.PropertyChanged += Model_PropertyChanged;
BindableControls = new Dictionary<string, Control>();
Model.Visible = false;
InitializeComponent();
RegisterBinding(boundButton, "Visible", Model, "Visible");
}
void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
foreach (var item in BindableControls)
{
NotifyChange(item.Value, e.PropertyName);
}
}
private void NotifyChange(Control control, string propertyName)
{
button1.DataBindings[propertyName].ReadValue();
}
private void RegisterBinding(Control control, string controlPropertyName, ViewModel _model, string modelPropertyName)
{
control.DataBindings.Add(controlPropertyName, _model, modelPropertyName, true, DataSourceUpdateMode.OnPropertyChanged);
BindableControls[control.Name] = control;
}
private void SetPropertyButton_Click(object sender, EventArgs e)
{
Model.Visible = true;
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool _IsVisible;
public bool Visible
{
get
{
return _IsVisible;
}
set
{
_IsVisible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I have defined a ViewModel object in the form that is used as binding data source for controls. (that have a Visible property)
private void RegisterBinding(Control control, string controlPropertyName, ViewModel _model, string modelPropertyName)
{
control.DataBindings.Add(controlPropertyName, _model, modelPropertyName, true, DataSourceUpdateMode.OnPropertyChanged);
BindableControls[control.Name] = control;
}
Use RegisterBinding method to register simple binding (parameters are simple enough).
'ViewModel' class implements INotifyPropertyChanged interface in System.ComponentModel. This interface adds a PropertyChanged event to call when any property in ViewModel changes.
In form's constructor I added event listener to PropertyChanged event of ViewModel in listener I forced the binding to read new value for each of controls that a binding registered for it. This portion of code refresh's the bound control and changes the visible state of button.
NOTE: In order to answer simplicity I assumed that properties in ViewModel that bound to a control's property has SAME name with destination property in form control. (Mode.Visible and boundButton.Visible). If you want to implement property name mapping for source and destination properties you can use a Dictionary or something to achieve this functionality.
I extended the button control to have also LabelName. When I press the button I need to write the name of the button in the label.
My first idea was using events - easy and simple.
The question is: Is there more elegant way to do it? (I've been asked to bind the button and the label)...
I think that the best way to do it would be to use an action listener and the best way to use the action listener would be to build it into your class that extends the button control so that the user doesn't have to do this on their own. It would look like this.
class Button2 : Button
{
public string LabelName = "";
public Button2()
{
this.Click += this.SetLabelName;
}
private void SetLabelName(object sender, EventArgs e)
{
this.LabelName = "Something?";
}
//You could also do this instead.
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
}
}
If you're talking about changing the Text property of an external Label control, then simply create a property in your Button to hold a reference to a Label. You can set this via the IDE like any other property:
Here's the Button class:
public class MyButton : Button
{
private Label _Label = null;
public Label Label
{
get { return _Label; }
set { _Label = value; }
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
if (this.Label != null)
{
this.Label.Text = this.Name;
}
}
}
Here's the Label after I clicked the Button: