Access control's properties from another class in C# WPF - c#

I'm in a mess with visibility between classes. Please, help me with this newbie question.
I have two controls (DatePickers from default WPF toolbox) which are in different windows, so in different classes. I can easily access these controls properties like datePicker1.Text from within its native class, i.e. in its native window, but when I try to reach datePicker1.Text from another window I get nothing.
I try to assign value of one datePicker to another, using reference to the window in my code:
string testStr;
...
AnotherWindow aw = new AnotherWindow();
testStr = aw.datePicker2.Text;
datePicker1.Text = testStr;
and it doesn't work
also I tried to do it through public property of a class, like:
public partial class AnotherWindow : Window
{
....
public string dateNearest
{
get { return datePicker2.Text; }
set { datePicker2.Text = value; }
}
....
and then use it in another window:
string testStr;
...
AnotherWindow aw = new AnotherWindow();
testStr = aw.dateNearest;
but also no value assigned.
Please, help me to understand this basic issue. I know there are other ways of accessing values in WPF like databinding, but I would like to understand basics first.

Unfortunately, the basics of WPF are data bindings. Doing it any other way is 'going against the grain', is bad practice, and is generally orders of magnitude more complex to code and to understand.
To your issue at hand, if you have data to share between views (and even if it's only one view), create a view model class which contains properties to represent the data, and bind to the properties from your view(s).
In your code, only manage your view model class, and don't touch the actual view with its visual controls and visual composition.

I'm using VS 2010 beta 2 right now which crashes regularly doing the simplest WPF coding, like trying to duplicate your question's code :) : but consider :
Is it possible that using this syntax will "do the right thing" :
public string dateNearest
{
get { return this.datePicker2.Text; }
set { this.datePicker2.Text = value; }
}
Edit 1 : Okay, I got a WPF replication of your code that didn't crash : using the above syntax I can both get and set the property in the "other window."
Edit 2 : The code also works using your original code :) Which, seemed to me to be "proper" the first time I read it. Are you setting that property before you read it ? : to my knowledge a DateTimePicker's Text property will be an empty string by default when first created.
Edit 3 : in response to Rem's request :
the main window has a button, 'button1 : which tests setting and getting the Public Property DTContent defined in the instance of the second Window named : 'WindowAdded : here's the 'Click event handler for that button in the main window's code :
private void button1_Click(object sender, RoutedEventArgs e)
{
WindowAdded wa = new WindowAdded();
wa.DTContent = DateTime.Now.ToString();
Console.WriteLine("dt = " + wa.DTContent);
}
Edit 4 : a better "real world" example : most cases you are going to want to create that instance of another window, and hold on to it, for re-use: imho : not have it exist only within the scope of a button's Click event. So consider, please :
Somewhere in the scope of the main window's code define a "place-holder" for the window(s) you will add : private WindowAdded wa;
In the event you select as most appropriate for creating the instance of that window : create the instance, and assign to your "place-holder" variable : then re-use it as needed. In WinForms I most often create required secondary windows that I will need to re-use references to the instances of to access something on them in the main form's load or shown events.
Discussion : of course, if your intent is to create "temporary" windows, and you don't need to re-use that reference to the new window's instance again, then creating it in the scope of some function is fine.
And, if the only thing you ever need to access on your second Window is the DateTimePicker, then you use the same technique suggested above, but create and hold to a reference to the instance of the DateTimePicker only.

As the others already pointed out, this is probably not the way to go, but you can use:
<object x:FieldModifier="public".../>
To set the object public.
See msdn for more info.

Related

Setting the properties of form values from another class

I have been working on an assignment with forms for a while now and everything works, however my professor want us to devide everything in seperate classes.
So what I have now is:
MainForm.cs
MainForm.Designer.cs
MainForm.resx
program.cs
In the MainForm.cs is where i have all my code and where i call the buttons, labels, textboxes etc from.
What I want to do, is to have a strucutre with other classes such as
MainForm.cs
MainForm.Designer.cs
MainForm.resx
program.cs
class1.cs
class2.cs
I tried doing this, but from my class1 i couldn't call the Design(name) of the Form since it dont exist in the context. I have been searching a lot but haven't found anything that matched my problem or how to solve it.
How can I solve this problem?
For any property to be accessible from another class (another form for instance as Form itself is a class), the property must be public, so for example lets say you have a textbox named txtSomething and you need to access its text, you may create a public property that lets you get and set its Text property:
public string SomeProperty { get { return txtSomething.Text;} set {txtSomething.Text = value;}}
You can of course change MainForm.Designer.cs and make all controls (for example textboxes) public where they are defined, but it is not a good choice at all. because you should always give public access only where it is needed. for example if you need a controls text, let just its text property be accessible (the code above).
Even, if the second form has just to get the textbox value and does need to set it, you may give a readonly access. so the code above would be:
public string SomeProperty { get { return txtSomething.Text;} }
Then assuming that the instance of FrmMain is frmMain you can access the Text property of that textbox like:
string propertyValue = frmMain.SomeProperty;

Visual Studio C# variable in properties

Is there any functionality to use a global variable in form properties window?
public partial class MainForm : Form
{
string title = "This is title";
// constructor,etc.
}
No, there isn't such functionality in the designer. The nearest you can get is using resources. You can't wire your resources directly in the designer (unless you are doing localization tricks), but you can edit your Designer.cs file and change:
this.MainForm.Text = "whatever";
for
this.MainForm.Text = Properties.Resources.MainFormTitle;
// or whatever other resource property you wish
This only works for resources (they are considered Global Objects by the designer and respected upon serializing): if you try to set any other variable (not a resource-generated property), it'll get overriden when the form is serialized again (upon saving).
Otherwise, you can just set your properties in the constructor, after InitializeComponents(), but they won't be seen in design-time unless you are inheriting that form.

Get the owner Form of a NotifyIcon?

In C# or Vb.Net, using managed or unmanaged code, how I could retrieve the owner Form of a NotifyIcon?
I've checked the base types of NotifyIcon Class and also the ComponentConverter trying to find out a possible type-casting to obtain the Form, but I was not able to.
I also seen the NotifyIcon.ContextMenuStrip.FindForm() function, but for any reason when I assign a contextmenustrip the FindForm() function always returns a null-reference exception, and anyways even if it could work will not be a safe approach because I could have a notifyicon without a contextmenu.
My intention is to pass a NotifyIcon as an argument to some methods that will perform common tasks to save me time and reduct code.
Pseudo example:
Public Shared Sub MinimizeToSystray(ByVal ntfy As NotifyIcon)
If (ntfy Is Nothing) Then
Throw New ArgumentNullException(paramName:="ntfy")
ElseIf (ntfy.Icon Is Nothing) Then
Throw New ArgumentException(message:="The NotifyIcon doesn't have an icon.",
paramName:="ntfy")
Else
Dim f As Form = ntfy.GetOwnerForm()
f.WindowState = FormWindowState.Minimized
f.Hide()
ntfy.Visible = True
End If
End Sub
The FindForm() method can only find the form for controls. The kind that derive from Control and are embedded in a form through its Parent property. But NotifyIcon is not a control, it is Component. A Component only has a Site property, its value is only defined at design time.
There is a casual relationship between a component and the form, Winforms promises to automatically dispose any components that have a constructor overload that takes an IContainer argument. Not all them do, OpenFormDialog and BackgroundWorker for example don't, NotifyIcon does. They omit the constructor when they don't need disposal.
Which makes it technically possible to find the form back. You'd need to iterate Application.OpenForms(). And use reflection to iterate their private components collection. Do note that this can only work when the form was actually opened, its Show() method must have been called.
That's a solution that scores -100 points, it is both ugly and error prone. The simple and always-correct solution is to just add an extra argument to the method to allow passing the "owner" form. With the assumption that, since the caller needs to know the NotifyIcon instance, it also should know the form. Typically Me.
Component doesn't have a FindForm method like controls and if you need such property you should customize the component and apply a workaround. Unfortunately NotifyIcon is sealed and can not be inherited.
As mentioned by Ivan Stoev in comments, you can use Tag property to store a reference to the container form.
But If you are looking for a designer based solution that works without writing such initialization codes to set the Tag, as a good option, you can create an extender component that adds a property ParentForm to your NotifyIcon component and set the property at design-time, then you can simply use it at run-time and when setting the ParentForm property at design-time you can set Tag property in component code, and then use it at run-time.
And here is the usage:
Add a the ComponentExtender (see the code at the end of post) to your project, then build the project. Then:
Add the ComponentExtender from toolbox to your form
Set the ParentForm on componentExtender1 property of your notifyIcon1 at designer using property grid:
Use this code to find the parent form of your notify icon:
var parent = this.notifyIcon1.Tag as Form`
Code
Here is an implementation that I used and works properly:
[ProvideProperty("ParentForm", typeof(NotifyIcon))]
public class ComponentExtender
: System.ComponentModel.Component, System.ComponentModel.IExtenderProvider
{
private Hashtable components;
public ComponentExtender() : base() { components = new Hashtable(); }
public bool CanExtend(object extendee)
{
if (extendee is Component)
return true;
return false;
}
public Form GetParentForm(NotifyIcon extendee)
{
return components[extendee] as Form;
}
public void SetParentForm(NotifyIcon extendee, Form value)
{
if (value == null)
{
components.Remove(extendee);
component.Tag = null;
}
else
{
components[extendee] = value;
component.Tag = value;
}
}
}

How to access a non-static property from another class

I have a non-static property inside a non-static MainForm class:
public string SelectedProfile
{
get { return (string)cbxProfiles.SelectedItem; }
set { cbxProfiles.SelectedItem = value; }
}
I would like to get the value of this property, from another non-static class. Using MainForm.SelectedProfile gives an error saying "An object reference is required for the non-static field, method or property".
Usually I would solve this problem by making SelectedProfile static, but I can't, since cbxProfiles (a ComboBox control) can't be made static.
So how do I access the property's value without making it static?
You access non-static members the same way you always do: by using a reference to an instance of the object.
So whatever code you want to be able to use that property, you need to pass it a reference to a MainForm object.
As said in the compilation error, you need to have a reference of the existing MainForm instance to act on it.
// You surely do this somewhere in your code
MainForm mainForm = new MainForm();
// ...
// Use the reference to your mainForm to access its public properties
String selectedProfile = mainForm.SelectedProfile;
I might be late to the party but my solution might help someone someday down the road. You can directly access an open form's controls (even private ones) using Application.OpenForms[n]...
For example, let's assume you have created a MainForm and then created a comboBox such that it is inside MainForm => Tab (named tabControl) => TabPage (named tabPageMain) => Panel (named pnlMain) => ComboBox (named cmbSeconds). Then you can access this last control as follows:
ComboBox combo = Application.OpenForms[0].Controls["tabControl"].Controls["tabPageMain"].Controls["pnlMain"].Controls["cmbSeconds"] as ComboBox;
string SelectedProfile = (string)combo.SelectedItem;
// OR
bool isMaximized = Application.OpenForms[0].WindowState == FormWindowState.Maximized;
i.e. you've got to traverse the path from the top-level form down to that particular control. Document Outline view of Visual Studio (View menu => Other Windows => Document Outline) might help you in this bcoz you might be overlooking some transparent containers in between.
Use it cautiously. For example, if the handles of any of the referenced controls are not yet created, you might see runtime exceptions.

Custom CollectionEditor doesn't serialize to aspx code

Context
I've been working on a custom collection editor / designer for a custom ASP.Net web control. The web control exposes a strange hierarchy, so a custom editor seemed like the right thing to do to make it easier for developers.
Building ASPX code and using the web control works. In other words, things like PersistChildren and ParseChildren are taken care of.
The signature of the property in the web control looks something like this:
[PersistenceMode(PersistenceMode.InnerProperty)]
[Themeable(false)]
[Browsable(false)]
public virtual DimensionsCollection Dimensions { get; internal set; }
Note that the property is not public; if it were public, all kinds of things in the designer will go wrong. DimensionsCollection is a class that simply inherits List<Dimension>. The Dimension class itself is nothing fancy, just a thing with some properties.
Just because I think it looks cool, I want to be able to modify the property from an action in the designer. To do that, I implemented a ControlDesigner class and added an ActionList. One of the actions there is a linkbutton that opens an editor:
var editor = new Editors.DimensionEditor(control.Dimensions);
if (editor.ShowDialog() == DialogResult.OK)
{ /* SEE BELOW */ }
The editor itself is a windows form that takes a List<Dimension> as constructor argument and modifies the collection.
Problem
When I use this code, I can see that the editor works and that the control collection is updated in the 'designer' view. If I open the editor multiple times, the state changes, meaning that somewhere in memory the state is updated by the editor.
However, if I go to the ASPX code, I can see that the Dimensions are not there anymore. So, the problem in a nutshell is that I somehow have to tell Visual Studio to write/serialize/persist the property to the ASPX file. (simple as that...)
Strangely, I cannot find anywhere how to do this... even though a normal CollectionEditor seems to be capable of doing just that (which I cannot subclass unfortunately)
Some things I tried
For other properties I noticed you have to use something like this, but this doesn't seem to work. Code was entered at the point marked as 'see below' or in some cases to a helper call in the designer called from that point:
PropertyDescriptor pd = TypeDescriptor.GetProperties(base.Component)["Dimensions"];
// use setter with internal property -> no effect
// this.OnComponentChanged(this, new ComponentChangedEventArgs(this.Component, pd, null, newdim)); -> no effect
// use getter to obtain list -> populate that using another list that's created in the editor
I can understand why it doesn't work; apparently someone has to tell Visual Studio that the property has changed... I just don't know how to do just that.
This was really a pain to figure out with apparently no sources online that explain how to do this.
Basically you want to use the OnComponentChanging / Changed methods to notify the designer. And apparently the designer uses transactions for the rest of the logic. (My guess is that it has to do with undo/redo behavior). For a normal type this is done automatically when you use the PropertyDescriptor, for collections it apparently doesn't wrap the collection which means you have to do it manually.
To solve the issue, you need to create a small method like this in either the UITypeEditor or in the DesignerActionList class your implementing:
private void ChangeAction(List<Dimension> newDimensions)
{
IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
PropertyDescriptor pd = TypeDescriptor.GetProperties(typeof(MyControl))["Dimensions"];
var dimensions = (DimensionsCollection)pd.GetValue(control);
var trans = host.CreateTransaction();
IComponentChangeService ccs = (IComponentChangeService)GetService(typeof(IComponentChangeService));
ccs.OnComponentChanging(control, pd);
dimensions.Clear();
dimensions.AddRange(newDimensions);
ccs.OnComponentChanged(control, pd, null, dimensions);
trans.Commit();
}
If you're implementing a UITypeEditor, make sure to use context.Instance from EditValue as the control and the given provider to lookup the services.

Categories

Resources