A PropertyGrid Issue - c#

Please consider my codes below:
I'm getting an error Constructor on type 'System.String' not found. when I add new string to the collection using the PropertyGrid control.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
propertyGrid1.SelectedObject = Class1.Instance.StringCollection;
}
}
-----------------------------------------------------------------------------
public sealed class Class1
{
private static Class1 _instance = new Class1();
private List<string> _stringListCollection = new List<string>();
public Class1()
{
}
public static Class1 Instance
{
get { return _instance; }
}
public List<string> StringCollection
{
get { return _stringListCollection; }
set { _stringListCollection = value; }
}
}

When you assign List of something to PropertyGrid, it tries to show single row with modify ... button,
where default modify dialog require Item class to have default constructor, which is not right in case of string
You can create class with default constructor and string property in it, and assign a collection of that class instead of string
Or you can use EditorAttribute to override default editor
Hope this helps

Here is a little class that implements CollectionEditor and fixes the problem for a list of strings:
public class CollectionEditorBase : CollectionEditor
{
public CollectionEditorBase(Type type) : base(type) { }
protected override object CreateInstance(Type itemType)
{
//Fixes the "Constructor on type 'System.String' not found." when it is an empty list of strings
if (itemType == typeof(string)) return string.Empty;
else return Activator.CreateInstance(itemType);
}
}
Now just change the editor to be used with you list of strings:
public class MySettings
{
[Editor(typeof(CollectionEditorBase), typeof(System.Drawing.Design.UITypeEditor))]
public List<string> ListOfStrings { get; set; } = new List<string>();
}
And then you us an instance of MySettings in the property grid:
propertyGrid1.SelectedObject = new MySettings();
At the top of your class, you would have to use System.ComponentModel and System.ComponentModel.Design or fully qualifies these names in your code.

Related

How can I access my method from another class

I'm very new to c#, I started a few days ago, so please excuse me if it is basic.
I have two forms, the first one is like a login page, where someone enters their name. On my "Info.cs" class, it reads this name via a setter, into a variable, and my Getter called "GetCardName" returns this Name. I now made a new form where I want to access this name via the GetCardName getter, just dont know how too. Heres the code :
Here is some of the "info.cs" class code:
private string CardName { get; set; } = "";
public string GetCardName()
{
return this.CardName;
}
public void SetName(string name = "")
{
this.CardName = name;
}
And here is the code from the other form that is just trying to call GetCardName():
private void Form2_Load(object sender, EventArgs e)
{
lblWelcome.Text = Info.GetCardName();
}
When creating Form2 you need also pass it reference to the other form to get its properties.
So when creating and showing Form1 you should also create Form2 to pass that reference. Example (not tested) code:
var form1 = new Form1();
var form2 = new Form2(form1);
form1.Show();
and Form2 should be like:
public class Form2
{
private Form1 _form1;
public Form2(Form1 form1)
{
_form1 = form1;
// ... other initialization code
}
// ... other class declarations
}
General solution is: you need to persist reference to the Form1 being shown to the user and then pass that reference to Form2 whenever you create it.
You have two options :
you can create an instance of the class that you want to call
EX : Info infoVar = new Info(); (now you can use infoVar to call any methods of the Info.cs class)
you can make Info class a STATIC class (probably not what you want to do, but still helpful for the future perhaps) This makes it possible to call the info class directly without having to create a variable of that class but has some drawbacks. (more info here)
There is few ways to achieve what you want:
Public static property:
public class Info
{
public static string CardName { get; set; } = string.Empty;
}
You can access it or set value to it directly by:
private void Form2_Load(object sender, EventArgs e)
{
// Set
Info.CardName = "Some name";
// Get
lblWelcome.Text = Info.CardName;
}
Public non-static property:
public class Info
{
public string CardName { get; set; } = string.Empty;
}
You can access it or set value to it directly too, but need to create Info class instance before:
private void Form2_Load(object sender, EventArgs e)
{
Info info = new Info();
// Set
info.CardName = "Some name";
// Get
lblWelcome.Text = info.CardName;
}
Private static field with separated public static Get and Set methods:
public class Info
{
private static string cardName = string.Empty;
public static string GetCardName()
{
return cardName;
}
public static void SetCardName(string name = "")
{
cardName = name;
}
}
You can access GetCardName and SetCardName without creating Info class instance:
private void Form2_Load(object sender, EventArgs e)
{
// Set
Info.SetCardName("Some name");
// Get
lblWelcome.Text = Info.GetCardName();
}
Private non-static field with separated public non-static Get and Set methods:
public class Info
{
private string cardName = string.Empty;
public string GetCardName()
{
return cardName;
}
public void SetCardName(string name = "")
{
cardName = name;
}
}
You can access GetCardName and SetCardName after creating Info class instance:
private void Form2_Load(object sender, EventArgs e)
{
Info info = new Info();
// Set
info.SetCardName("Some name");
// Get
lblWelcome.Text = info.GetCardName();
}
Difference between fields and properties was pretty nice explained here: What is the difference between a field and a property?. In short, properties are "wrappers" over fields, which usually are private and you can't access to them directly or modify. It is a part of Member Design Guidelines. Also properties allow to add some validations through property setter to be sure that valid value is stored at cardName field, e.g.:
public class Info
{
private string cardName = string.Empty;
public string CardName
{
get => cardName;
set
{
// Check that value you trying to set isn't null
if (value != null)
cardName = value;
// Or check that name is not too short
if (value.Length >= 3) // Card name should be at least of 3 characters
cardName = value;
}
}
}
info myInfo=new info();
lblWelcome.Text = myInfo.GetCardName();

Panel does not contain a constructor that takes 0 arguments

I need to load a User Control in my panel1 inside Form1.cs, the problem is that the UserControl (AudioPlaybackPanel) contains an ImportingConstructor ([ImportMany]IEnumerable<>) and I can't figure out what two arguments I should have in the Form1 AudioPlaybackPanel(????).
The error I get is: 'NAudio.App.AudioPlaybackPanel' does not contain a constructor that takes 0 arguments
Here is the Form1.cs
namespace NAudio.App
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
AudioPlaybackPanel myPanel = new AudioPlaybackPanel(????);
panel1.Controls.Add(myPanel);
}
}
}
And this is my User Control Panel (AudioPlaybackPanel.cs):
namespace NAudio.App
{
[Export]
public partial class AudioPlaybackPanel : UserControl
{
private IWavePlayer waveOut;
private string fileName = null;
private WaveStream fileWaveStream;
private Action<float> setVolumeDelegate;
[ImportingConstructor]
public AudioPlaybackPanel([ImportMany]IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
InitializeComponent();
LoadOutputDevicePlugins(outputDevicePlugins);
}
[ImportMany(typeof(IInputFileFormatPlugin))]
public IEnumerable<IInputFileFormatPlugin> InputFileFormats { get; set; }
private void LoadOutputDevicePlugins(IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
comboBoxOutputDevice.DisplayMember = "Name";
comboBoxOutputDevice.SelectedIndexChanged += new EventHandler(comboBoxOutputDevice_SelectedIndexChanged);
foreach (var outputDevicePlugin in outputDevicePlugins.OrderBy(p => p.Priority))
{
comboBoxOutputDevice.Items.Add(outputDevicePlugin);
}
comboBoxOutputDevice.SelectedIndex = 0;
}
void comboBoxOutputDevice_SelectedIndexChanged(object sender, EventArgs e)
{
panelOutputDeviceSettings.Controls.Clear();
Control settingsPanel;
if (SelectedOutputDevicePlugin.IsAvailable)
{
settingsPanel = SelectedOutputDevicePlugin.CreateSettingsPanel();
}
else
{
settingsPanel = new Label() { Text = "This output device is unavailable on your system", Dock=DockStyle.Fill };
}
panelOutputDeviceSettings.Controls.Add(settingsPanel);
}
private IOutputDevicePlugin SelectedOutputDevicePlugin
{
get { return (IOutputDevicePlugin)comboBoxOutputDevice.SelectedItem; }
}
// The rest of the code continues from here on...
}
}
Here is the Interface:
namespace NAudio.App
{
public interface IOutputDevicePlugin
{
IWavePlayer CreateDevice(int latency);
UserControl CreateSettingsPanel();
string Name { get; }
bool IsAvailable { get; }
int Priority { get; }
}
}
And just in case, here is one of the plugins:
DirectSoundOutPlugin.cs
namespace NAudio.App
{
[Export(typeof(IOutputDevicePlugin))]
class DirectSoundOutPlugin : IOutputDevicePlugin
{
private DirectSoundOutSettingsPanel settingsPanel;
private bool isAvailable;
public DirectSoundOutPlugin()
{
this.isAvailable = DirectSoundOut.Devices.Count() > 0;
}
public IWavePlayer CreateDevice(int latency)
{
return new DirectSoundOut(settingsPanel.SelectedDevice, latency);
}
public UserControl CreateSettingsPanel()
{
this.settingsPanel = new DirectSoundOutSettingsPanel();
return this.settingsPanel;
}
public string Name
{
get { return "DirectSound"; }
}
public bool IsAvailable
{
get { return isAvailable; }
}
public int Priority
{
get { return 3; }
}
}
}
Please help!
The error doesn't say it expects two arguments... it just says it doesn't take 0.
The constructor expects a single parameter - an IEnumerable<IOutputDevicePlugin>:
public AudioPlaybackPanel([ImportMany]IEnumerable<IOutputDevicePlugin> outputDevicePlugins)
{
...
}
You need to find something that implements the IOutputDevicePlugin interface and pass a collection of it, even if it's just an empty collection. (Passing null to the constructor will allow it to compile but will throw a runtime exception when you hit the loop in LoadOutputDevicePlugins.)
Considering the update to your question, something like this will get you up and running (although I doubt it means very much to pass an empty list):
var myPanel = new AudioPlaybackPanel(new List<DirectSoundOutPlugin>());
panel1.Controls.Add(myPanel);
It's worth asking whether you actually need to copy AudioPlaybackPanel.cs from the NAudio demo in its entirety. The reason it has this constructor is that it tries to demonstrate how you can use each and every one of NAudio's IWavePlayer implementations. But in a normal real-world application you would just select the one that was most appropriate for your use. e.g.
this.waveOut = new WaveOut();
waveOut.Init(new AudioFileReader("my file.mp3");
waveOut.Play();
So there's no need to incorporate the plug-in architecture from that particular demo, if all you want is just to play audio files.

List of Inherited Classes Listbox Class Name not Valid at this Point

I am creating a WinForms application and I have a Listbox with a datasource of an ObservableCollection<ParentClass> and I am trying to set specific labels based on the subclass of the class. I get the error "Class name is not valid at this point". Sample code:
using System;
public class Parent
{
public Parent() { }
public class ChildA : Parent
{
public ChildA() { }
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ObservableCollection<Parent> listBoxSource =
new ObservableCollection<Parent>();
listBox.DataSource = listBoxSource;
}
private void customerListBox_SelectedIndexChanged(object sender,
EventArgs e)
{
if (customerListBox.SelectedItem.GetType() ==
Parent.ChildA) // <---Error Here
{
//Code Here
}
}
}
Is there a better way to perform an action based on the type of an element?
change this:
if (customerListBox.SelectedItem.GetType() == Parent.ChildA)
to:
if (customerListBox.SelectedItem is Parent.ChildA)
or as you was doing:
if (customerListBox.SelectedItem.GetType() == typeof(Parent.ChildA))
Realize that using operator "is" avoid to check if the object is null.

Bind a class collection property via xaml

This is my Globals class
public class Globals
{
private static Globals instance = new Globals();
protected Globals()
{
}
public static Globals Instance
{
get { return instance; }
}
public TrackList Tracklist = new TrackList();
}
This is TrackList in a smart code:
public class TrackList : SettingAttribute {
public TrackList()
{
this.tracks = new ObservableCollectionExt<Track>();
}
protected ObservableCollectionExt<Track> tracks;
public ObservableCollectionExt<Track> Tracks
{
get
{
return tracks;
}
}
public class Track : ICloneable
{
protected Track()
{
// Track instance is achieved by GetTrack
}
public GetTrack(string path)
{
// code implementation here
}
}
}
I wish to bind Globals.Instance.Tracklist.Tracks in a ListView using XAML.
Via runtime, is really easy using ItemSource property
lv.ItemsSource = Globals.Instance.Tracklist.Tracks;
but using xaml I tried with several codes but none is good.
ItemsSource="{Binding Tracklist.Tracks, Source={x:Static local:Globals.Instance}}"
Tracklist has to be a property. Change your Globals class to:
public class Globals
{
private static Globals instance = new Globals();
protected Globals()
{
Tracklist = new TrackList();
}
public static Globals Instance
{
get { return instance; }
}
public TrackList Tracklist { get; private set; }
}
In you view model create Property with type Globals as follows:
property Globals Globals {get;set;}
In XAML bind to it:
<ListView ItemsSource="{Binding Path=Globals.Instance.Tracklist.Tracks}">

Using the .NET collection editor without using a property grid control

I have a PropertyGrid on my form. My boss thinks it's ugly. Uncouth. Unsophisticated.
He wants a nice, neat, clean form. Here's the catch: One of the properties is a collection of our home-grown objects. He likes the collection editor for this collection.
I know I can build my own collection editor. But is there a clean, simple solution to save me a few hours of coding, such that I can create and use a Collection editor directly without using the property grid?
You can get this functionality from the UITypeEditor (via TypeDescriptor), but it isn't trivial - you need to set up an IServiceProvider, an IWindowsFormsEditorService, and ideally an ITypeDescriptorContext - quite a bit of faff. It might be simpler to do it by hand if you aren't familiar with those tools.
Alternatively - take a look at SmartPropertyGrid.NET, an alternative to PropertyGrid.
Update: here's a working example... definitely non-trivial, but feel free to steal the code. It only works for modal editors, not drop-down. It also isn't a great example of "separation of concerns". The MyHelper class is the interesting one.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
class Foo
{
public Foo() { Bars = new List<Bar>(); }
public List<Bar> Bars { get; private set; }
}
class Bar
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
static class Program
{
[STAThread]
static void Main()
{
Foo foo = new Foo();
Bar bar = new Bar();
bar.Name = "Fred";
bar.DateOfBirth = DateTime.Today;
foo.Bars.Add(bar);
Application.EnableVisualStyles();
using(Form form = new Form())
using (Button btn = new Button())
{
form.Controls.Add(btn);
btn.Text = "Edit";
btn.Click += delegate
{
MyHelper.EditValue(form, foo, "Bars");
};
Application.Run(form);
}
}
}
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName) {
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if(prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor) prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if(editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{}
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
}

Categories