I'm working on an application that supports various plugins (well, that's planned at least). And I'd love to have it beautiful.
So I want to let the plugin developer send a big control (like a panel or other containers) to my host application and have the user setup their settings for the plugin in the application.
That would take the plugin-developer's effort to somehow implement a settings-panel that runs by in an own window.
Thing is, I'm not sure how to do that.
I can pass variables to my host application but as soon as I try to add the control to my container panel, I get a RemoteException, telling me that the field 'parent' on type 'System.Windows.Forms.Control' can't be found.
I tried to add the plugin-control that way:
panel.Controls.Add(pluginControl);
If I try it the other way around:
pluginControl.Parent = panel;
I get a SerializationException because the class System.Windows.Forms.Control isn't marked Serializable.
Maybe some person ran into the same thing and can help me.
Let me know if you need more information!
Edit: Have a look on my current implementation: https://dl.dropboxusercontent.com/u/62845853/Random%20crap/NotModified_SamplePluginSystem.zip
I tried something which you can adorn to your needs:
First i created a PluginBase class and the proper EventArgs in a ClassLibrary:
public abstract class PluginBase
{
public abstract void Initialize();
protected void showControl(UserControl control)
{
ShowControl(this, new ControlToBeShownEventArgs() { TheControl = control });
}
public event EventHandler<ControlToBeShownEventArgs> ShowControl = delegate { };
}
public class ControlToBeShownEventArgs : EventArgs
{
public UserControl TheControl { get; set; }
}
This library is referenced by every Plugin and by the host application.
The Plugin is in turn also a Class Library (build path set to the one of the host)
inside i made a plugin inheriting this base type:
class SomePlugin : PluginBase
{
public override void Initialize()
{
showControl(new UserControl1());
}
}
The UserControl1 is the Control to be shown.
Done that, I next added the following code to the main window of the host:
List<PluginBase> plugins = new List<PluginBase>();
private void Form1_Load(object sender, EventArgs e) //Hook in the event too
{
DirectoryInfo dir = (new FileInfo(Assembly.GetExecutingAssembly().Location)).Directory;
foreach (var item in dir.GetFiles())
{
if (item.Name.Contains("Plugin") && item.Name.EndsWith(".dll"))
{
Assembly ass = Assembly.LoadFile(item.FullName);
foreach (Type type in ass.GetTypes().Where(t => t.BaseType.Name == "PluginBase"))
{
PluginBase pibase = (PluginBase)Activator.CreateInstance(type,false);
plugins.Add(pibase);
}
}
}
foreach (var item in plugins)
{
item.ShowControl += item_ShowControl;
item.Initialize();
}
}
void item_ShowControl(object sender, ControlToBeShownEventArgs e)
{
this.Controls.Add(e.TheControl);
}
Related
UPDATE: I tried working out by making every class in Code public, but it doesn't seem to accept my class hierarchy.
This is my first post so please bear with me. Besides that, i'm a pretty big noob, so do excuse me if something dumb comes along
I'm currently doing a school project and I have a fully fleshed out .NET Framework project in Visual Studio. Now I have to visualize it with a WPF app. So I made a new WPF project in the same source.
Let's respectively call them Code and Visualization.
I've given Visualization a project reference to Code and put using Code; at the top of the XAML.cs
I made a button in Visualization and I want it's OnClick event to use
Code.Start();
What somehow seems to sort of work is making every class in Code public, but I don't remember that being a good practice, but do correct me if i'm wrong!
I've put multiple hours into finding a solution with none to be really found before. Seeing as nobody seems to have posted this question before I must be missing something really simple.
Cheers!
namespace Code
{
class Program
{
public void Start()
{
/// Do something
}
}
}
using Code;
namespace Visualization
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Start_Click(object sender, RoutedEventArgs e)
{
// Use the Start() function from Code
}
}
}
To use classes and their functions and properties from another project, you have to declare them as public.
namespace Code
{
public class Program
{
public void Start()
{
// Do something
}
}
}
Then go to the other project, right click > Add > Reference > select your project containing the code above (assuming you're using Visual Studio IDE). After, you can access the public functions and properties:
using Code;
namespace Visualization
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Start_Click(object sender, RoutedEventArgs e)
{
Program p = new Program();
p.Start();
}
}
}
If you are worried about security, then ensure that public functions and properties you deem are safe to expose to other projects. For example, what we just did to use the Start function, any other project or some 3rd party program could do also. The only difference is that a reference would be made to the project's .dll produced instead of the project itself.
A basic rule of thumb (at least for me) is that if there are anything I don't want to expose, then don't make them public and have a public function that can be called to perform different actions. This way I can limit what actions and information can be performed or accessed:
//within some project
namespace Code
{
public class Program
{
// can't be access from another project directly
private string _privateText { get;set; }
// can be accessed directly
public string PublicText { get;set; }
public void Start()
{
// Do something
}
public string getPrivateText()
{
// here you can limit what actions are done and what information to return
return _privateText;
}
}
}
You can then do the following:
// within another project
using Code;
namespace Visualization
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Start_Click(object sender, RoutedEventArgs e)
{
Program p = new Program();
string s1 = p.getPrivateText();
string s2 = p.PublicText;
p.Start();
}
}
}
Hope this helps!
I follow official guide to use simple injector to inject object in web form and it works, but now i cant make it works in custon control
this is what i do:
public partial class GestioneAttivita_cnStruttureSocieta : System.Web.UI.UserControl
{
[Import]
public IUnitOfWork iuow { get; set; }
public Domain.Entity.Attivita attivitaGestita {get; set;}
protected void Page_Load(object sender, EventArgs e)
{
using (iuow)
{
attivitaGestita = iuow.Attivita.Read(attivitaGestita.IdAttivita);
}
}
}
but i get null reference exception since iuow is null
i try to edit global.asax to manage UserControl in this way:
private static void RegisterWebPages(Container container)
{
var pageTypes =
from assembly in BuildManager.GetReferencedAssemblies().Cast<Assembly>()
where !assembly.IsDynamic
where !assembly.GlobalAssemblyCache
from type in assembly.GetExportedTypes()
where (type.IsSubclassOf(typeof(Page)) **|| type.IsSubclassOf(typeof(UserControl)))**
where !type.IsAbstract && !type.IsGenericType
select type;
foreach (Type type in pageTypes)
{
var registration = Lifestyle.Transient.CreateRegistration(type, container);
registration.SuppressDiagnosticWarning(
DiagnosticType.DisposableTransientComponent,
"ASP.NET creates and disposes page classes for us.");
container.AddRegistration(type, registration);
}
}
}
class ImportAttributePropertySelectionBehavior : IPropertySelectionBehavior {
public bool SelectProperty(Type serviceType, PropertyInfo propertyInfo) {
// Makes use of the System.ComponentModel.Composition assembly
bool _return = false;
_return = (typeof(Page).IsAssignableFrom(serviceType) &&
propertyInfo.GetCustomAttributes<ImportAttribute>().Any())
**||
(typeof(UserControl).IsAssignableFrom(serviceType) &&
propertyInfo.GetCustomAttributes<ImportAttribute>().Any());**
return _return;
}
}
but i get same error
is this doable?
To be able to get this working, you will have to hook onto the PreLoad event of the page during initialization. During PreLoad you can walk the page's control hierarchy and initialize all controls like you do with the page itself.
There's actually example code in the Simple Injector repository (that never made it to an official package) that shows you how to do this. Take a look here.
I've just created a solution and added a 'Component Class' to it.
All I need is to add a menu to Component Class when it is in the componentbar of a win-form, like the ImageList component of .NET.
Can anyone help me?
I assume you are referring to the little tiny arrow that appears on the ImageList component when you select it and you see a list of options. That requires a custom ComponentDesigner.
Make references to:
System.Components.Design
System.Design
System.Windows.Forms.Design
Here is a simple little component example:
[Designer(typeof(TestComponentDesigner))]
public class TestComponent : Component {
public class TestComponentDesigner : ComponentDesigner {
private DesignerVerbCollection verbs = new DesignerVerbCollection();
public override void Initialize(IComponent component) {
base.Initialize(component);
verbs.Add(new DesignerVerb("Say Hello", new EventHandler(SayHello)));
}
public override DesignerVerbCollection Verbs {
get {
return verbs;
}
}
private void SayHello(object sender, EventArgs e) {
MessageBox.Show("Hello");
}
}
}
Results:
For more information, see Writing Custom Designers for .NET Components
I am getting stumped with my plug-in architecture that I am trying to develop with respect to events. I can do this just fine, in a single application: (Obviously this is a very simplified version of what I am trying to accomplish, and if this were my code there are easier ways to accomplish this, just try to follow the logic ;)
public Form1()
{
public event EventHandler OnValueOver5Chars;
Main()
{
OnValueOver5Chars+= new EventHandler(WarnUser);
....
}
private void textBox_Changed( object sender, EventArgs e )
{
if( sender.Text.count() > 5 )
OnValueOver5Chars(sender, e); // WORKS HERE
}
private void WarnUser(object sender, EventArgs e)
{
...
}
}
However, now I have a plug-in architecture, where the plugin implements an interface which houses my event:
// Interface.cs
public interface IPlugin
{
event EventHandler OnValueOver5Chars;
...
}
// Plugin1.cs
public class Plugin1 : IPlugin
{
public event EventHandler OnValueOver5Chars;
Plugin1()
{
OnValueOver5Chars += new EventHandler(Plugin1WarnUser);
}
private void Plugin1WarnUser(object sender, EventArgs e)
{
...
}
}
// Form.cs
public class Form1 : Form
{
public Form1()
{
Assembly SampleAssembly = Assembly.LoadFrom("Plugin1.dll");
Type myType = SampleAssembly.GetTypes()[0];
if (myType.GetInterfaces().Contains(typeof(IPlugin)))
{
IPlugin myInstance = Activator.CreateInstance(myType) as IPlugin;
myInstance.OnValueOver5Chars(this, new EventArgs());
// Compiler Error CS0079 - The event 'event' can only appear on the left hand side of += or -=
}
}
????
You're trying to hook up an event to another event, and that won't work. You need to hook the plug-ins event to a method/delegate. Once you do that, have the method/delegate call the other event.
myInstance.OnValueOver5Chars += OnValueOver5CharsFunc;
...
/*In Form1*/
void OnValueOver5CharsFunc( object sender, EventArgs args )
{
OnValueOver5Chars( sender, args );
}
Events in C# have the property that they are not "callable" directly as methods (or as such as delegates) outside of the class where they are defined.
In your first example you are calling the event from within the class in which you define it. In the second example, however, you are trying to call OnValueOver5Chars from outside the class - hence the error.
To solve this you could consider adding a method to your IPlugin interface (e.g. ValueOver5Chars) that performs OnValueOver5Chars. Note that it is more common to name the event ValueOver5Chars (say), and provide a method OnValueOver5Chars to raise it (i.e. the other way round). See for example the Windows Forms Button class and its Click event.
In ASP.Net Web Forms there are cases that I've come across that create order dependent code. As an obvious code smell I'm looking for solutions to solve this problem.
A pseudo-code example would be:
Calling Code :: Page.aspx
protected void Page_Load(...) {
var control = LoadControl("ControlX.ascx");
// Ugly since the control's constructor is not used by LoadControl
control.SetDependencies(...);
}
Control Code :: ControlX.ascx
public void SetDependencies(...) {
}
protected void Page_Load(...) {
if (dependencies are null)
throw Exception();
else
ContinueProcessing();
}
LoadControl has two signatures, the one used above accepts a string for the control classes physical location and correctly creates the child controls. Whereas the second signature accepts the control class as a class type, and any parameters for the constructor, however the child controls are not created as detailed in TRULY Understanding Dynamic Controls.
So how can I eliminate this order dependency in the cleanest way? My first thought is that if I dynamically created the child controls in ControlX, but then that can be cumbersome for larger controls. Thoughts?
(I hope in understood the problem correctly) You could invert the dependecy like this:
The host of ControlX.ascx (either another control or a page) must implement a certain interface (defined by ControlX). ControlX can then access its dependencies from its host through that interface.
A small example would be this:
public interface IControlXHost
{
// methods, properties that allow ControlX to access its dependencies
int GetStuff();
}
public partial class ControlX : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
var host = (Parent as IControlXHost) ?? (Page as IControlXHost);
if (host == null) {
throw new Exception("ControlX's parent must implement IControlXHost");
}
else {
int number = host.GetStuff();
}
}
}
The host (the page or the control hosting ControlX) would then have to implement that interface, e.g:
public partial class Default4 : System.Web.UI.Page, IControlXHost
{
public int GetStuff() {
return 33;
}
protected void Page_Load(object sender, EventArgs e) {
var control = LoadControl("ControlX.ascx");
}
}
IMO, this approach makes controls easier reusable, since they "automatically" tell you about the requirements that have to be fulfilled to host the control. You don't have to find out which methods of the control you have to call in which order.