MEF. How to load a winform into winform container? - c#

I have decided to play a little bit with MEF2 and net3.5 and I have thought it would be easy but I am stuck now. Generally the idea of my toy is I want to have form containet where I am going to load form extensions and show them. I did this code
My extension:
using System.ComponentModel.Composition;
using System.Windows.Forms;
namespace MyExtantion
{
public interface IForm
{
void LoadForm(Form form);
}
[Export(typeof(IForm))]
public partial class MyExtantion : Form, IForm
{
public MyExtantion()
{
InitializeComponent();
}
public void LoadForm(Form form)
{
MdiParent = form;
Show();
}
}
}
and form container
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Windows.Forms;
namespace FormsContainer
{
public partial class FormContainer : Form
{
public FormContainer()
{
InitializeComponent();
}
private CompositionContainer _container;
public interface IForm
{
void LoadForm(Form form);
}
[Import(typeof(IForm))]
public IEnumerable Forms { get; set; }
private bool Compose()
{
var catalog = new AggregateCatalog(
new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new DirectoryCatalog("Extantions"));
var batch = new CompositionBatch();
batch.AddPart(this);
_container = new CompositionContainer(catalog);
try
{
_container.Compose(batch);
}
catch (CompositionException compositionException)
{
MessageBox.Show(compositionException.ToString());
return false;
}
return true;
}
private void FormContainer_Load(object sender, EventArgs e)
{
if (Compose())
foreach (IForm form in Forms)
{
form.LoadForm(this);
}
}
}
}
The problem is I can not load my extantion and I have this error
{"The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.\r\n\r\n1) No exports were found that match the constraint '((exportDefinition.ContractName = \"FormsContainer.FormContainer+IForm\") && (exportDefinition.Metadata.ContainsKey(\"ExportTypeIdentity\") && \"FormsContainer.FormContainer+IForm\".Equals(exportDefinition.Metadata.get_Item(\"ExportTypeIdentity\"))))'.\r\n\r\nResulting in: Cannot set import 'FormsContainer.FormContainer.Forms (ContractName=\"FormsContainer.FormContainer+IForm\")' on part 'FormsContainer.FormContainer'.\r\nElement: FormsContainer.FormContainer.Forms (ContractName=\"FormsContainer.FormContainer+IForm\") --> FormsContainer.FormContainer\r\n"}
How I can achieve it with MEF? and What I do wrong?

You are declaring the IForm interface in two different places.
If you only reference one interface that both are using this code works properly.

Related

c# unable to interact with listbox from another form

I have the following form with a method called setIndex
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ProjectName
{
public partial class SettingsWindow : Form
{
internal readonly static SettingsWindow Instance = new SettingsWindow { Visible = false };
public SettingsWindow()
{
InitializeComponent();
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
string defaultsearch = listBox1.GetItemText(listBox1.SelectedItem);
Core.RegistryHelper.SaveSetting("Config", "ds", defaultsearch);
if (defaultsearch == "aaa") {
Core.LandingUrlOrig = Core.DomainName + "/defaulturl1.php";
} else {
Core.LandingUrlOrig = Core.DomainName + "/defaulturl2.php";
}
}
public static void setIndex(int i)
{
listBox1.SelectedIndex = i;
}
On another form called MainWindow during its initialization I call:
public MainWindow()
{
SettingsWindow.setIndex(0);
}
The error I get is:
An object reference is required for the non-static field, method, or property 'SettingsWindow.listBox1'
Initially the listbox method wasn't static and thus invisible from MainWindow. But now, listbox appears to not exist, even if the form has been instantiated. How do I solve this? I'm just learning C#.
Thank you in advance
because the function setIndex is static you need to use the Instance property:
public static void setIndex(int i)
{
Instance.listBox1.SelectedIndex = i;
}
or don't make that function static and then use instance in the mainwindow function:
public void setIndex(int i)
{
listBox1.SelectedIndex = i;
}
public MainWindow()
{
SettingsWindow.Instance.setIndex(0);
}

Close with timeout is not working in Console to close splashscreen

I have made a class, I make an instance of. In said instance I have these lines of code to show and close the splashscreen.
// Open (show)
public void ShowSplashScreen(bool autoClose = false)
{
splashscreen.Show(autoClose, true);
}
// Close (don't show)
public void CloseSplashScreen()
{
splashscreen.Close(TimeSpan.FromSeconds(0.3));
}
It shows up fine, but never closes, just stays there.
This is the documentation of splashscreen Close: https://learn.microsoft.com/en-us/dotnet/api/system.windows.splashscreen.close?view=netframework-4.8
[System.Security.SecurityCritical]
public void Close (TimeSpan fadeoutDuration);
Note: I am using the show method with the parameters AutoClose set to false, and TopMost set to true, this makes it not auto close as I want to close it programmatically and not subscribe to existing events.
I am running the lines of code from a Console (.NET framework) application for testing purposes before implementing it into my UI fully.
What I have tried:
Debugging and even trying to call show again before calling close.
It is definitely something going wrong with the class, as calling the class and directly manipulating the property works:
ClassSplashScreen rss = new ClassSplashScreen();
rss.splashscreen.Show(false);
rss.splashscreen.Close(TimeSpan.FromSeconds(1));
My best guess is something is hanging the UI and freezing it? But I am unsure what to do about it.
Code to run to test this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace NamespaceName
{
public class StackOverFlowCode
{
static void Main(string[] args)
{
ClassSplashScreen screen = new ClassSplashScreen();
screen.ShowSplashScreen();
screen.CloseSplashScreen();
}
}
public class ClassSplashScreen
{
public SplashScreen splashscreen { get; set; }
public ClassSplashScreen()
{
splashscreen = new SplashScreen("Resource Image Link");
}
public void ChangeSplashResource(SplashScreen resource)
{
splashscreen = resource;
}
public void ShowSplashScreen(bool autoClose = false)
{
splashscreen.Show(autoClose, true);
}
public void CloseSplashScreen()
{
splashscreen.Close(TimeSpan.FromSeconds(1));
}
}
}
The SplashScreen relies on a dispatcher but there is no one in a console application by default. If you create a System.Windows.Application, it should work as expected:
public class StackOverFlowCode
{
[STAThread]
static void Main(string[] args)
{
Application app = new Application();
app.Startup += (s, e) =>
{
ClassSplashScreen screen = new ClassSplashScreen();
screen.ShowSplashScreen();
screen.CloseSplashScreen();
};
app.Run();
}
}
public class ClassSplashScreen
{
private readonly SplashScreen splashscreen;
public ClassSplashScreen() => splashscreen = new SplashScreen("Resource Image Link");
public void ShowSplashScreen() => splashscreen.Show(false);
public void CloseSplashScreen() => splashscreen.Close(TimeSpan.FromSeconds(1));
}

"ExportToDataGridView" Does not exist in .NET GemBox

Ive been trying to find why this happend but for some reason .net of gembox.spreadsheet.winformutilities wont provide ExportToDataGridView on the code:
using System.Windows.Forms;
using GemBox.Spreadsheet;
using GemBox.Spreadsheet.WinFormsUtilities;namespace Excel
{
public partial class UserControl1 : UserControl
{
private void bunifuFlatButton2_Click(object sender, EventArgs e)
{
OpenFileDialog open = new OpenFileDialog();
open.Filter = "Al files (*.*)|*.*|";
open.FilterIndex = 1;
if (open.ShowDialog()== DialogResult.OK)
{
ExcelFile ef = new ExcelFile();
ExcelWorksheet ws = ef.Worksheets.Add("Export");
DataGridViewConverter.***ExportToDataGridView***(ef.Worksheets.ActiveWorksheet, this.dataGridView1, new ExportToDataGridViewOptions() { ColumnHeaders = true });
}
}
}
}
thank you for your answer in advance!
The problem occurred because of the name collision:
namespace Excel
{
public partial class UserControl1 : UserControl
{
public static class DataGridViewConverter
{
}
}
}
So there are two classes of same name:
Excel.UserControl1.DataGridViewConverter
GemBox.Spreadsheet.WinFormsUtilities.DataGridViewConverter
The solution is to either use the class's full name, or you could define an alias name, for example:
// ...
using System.Windows.Forms;
using GemBox.Spreadsheet;
using GemBoxDataGridViewConverter = GemBox.Spreadsheet.WinFormsUtilities.DataGridViewConverter;
namespace Excel
{
public partial class UserControl1 : UserControl
{
private void bunifuFlatButton2_Click(object sender, EventArgs e)
{
GemBoxDataGridViewConverter.ExportToDataGridView(...);
}
}
}
Also as an FYI, you can download a working example from GitHub or check the Windows Forms online example.

Return Value Between Classes

How do I get a button click on a form to send the return of a called method to another class? Here is the pseudo code of what I have and any help would be greatly appreciated...
[Class Library]
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
using System.Linq;
namespace Auto
{
GUID Info
public interface IAuto
{
string SendToOtherApp();
}
COM Info
public class Auto : IAuto
{
public string tbox1;
NAVForm frm1 = new NAVForm();
public Auto()
{
}
public string SendToOtherApp()
{
frm1.ShowDialog();
tbox1 = NAVForm.UseThis();
return tbox1;
}
}
}
[Form]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Auto
{
public partial class NAVForm : Form
{
public NAVForm()
{
InitializeComponent();
}
private void NAVForm_Load(object sender, EventArgs e)
{
}
private void button2_Click(object sender, EventArgs e)
{
UseThis(textBox1.Text);
}
public string UseThis(string txt)
{
if (txt.Trim().Length != 0)
{
return txt;
}
else
{
return "didn't work";
}
}
}
}
I want to get the return value from public string UseThis(string txt) into public string SendToOtherApp() which is visible to the other system that is calling this.
I am obviously new to C# so I am also very open to an overall critique of the project and best practices.
This is what I have done and it works great. In our ERP I run the codeunit, which calls the automation variable which is tied to the "OpenThis()" method. My form opens, I enter text in the textbox, click OK, it closes the from and the ERP pops a messagebox displaying the text from the message box. What do you C# experts think about this build? I am very interested in your thoughts on this solution so please let me know.
Class Library.....
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
using System.Linq;
namespace NavAutomation
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("5D83B4FE-45E6-410E-A075-AD635F5F0354")]
[ComVisible(true)]
public interface INavAutomation
{
string HelloWorld();
object OpenThis();
}
[ComVisible(true)]
[Guid("B7806CE5-862A-4407-9A3E-14CE8A9FB83A")]
[ClassInterface(ClassInterfaceType.None)]
public class NavAutomation : INavAutomation
{
public NavAutomation()
{
}
public object OpenThis()
{
using (var form = new NAVForm())
{
var result = form.ShowDialog();
return form.RetVal1;
}
}
}
}
Form.....
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace NavAutomation
{
public partial class NAVForm : Form
{
public NAVForm()
{
InitializeComponent();
}
private void NAVForm_Load(object sender, EventArgs e)
{
}
public string RetVal1 { get; set; }
private void button2_Click(object sender, EventArgs e)
{
if (textBox1.Text.Trim().Length != 0)
{
this.RetVal1 = textBox1.Text;
}
else
{
this.RetVal1 = "didn't work";
}
this.Close();
}
}
}
I am not sure if i got your goals right but here is the code that when called from a from, shows another modal form with a textbox, you enter a value into that textbox and close this modal form to find that value in that textbox returned to the first form that called for the show of the modal form.
CLASS LIBRARY
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections.Generic;
using System.Linq;
namespace Auto
{
public interface IAuto
{
string SendToOtherApp();
}
public class Auto : IAuto
{
public string tbox1;
NAVForm frm1 = new NAVForm();
public Auto()
{
}
public string SendToOtherApp()
{
frm1.ShowDialog();
tbox1 = frm1.UseThis(frm1.textBox1.Text);
return tbox1;
}
}
}
A FROM THAT CALLS TO SHOW A MODAL FORM
namespace Auto
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Auto auto = new Auto();
string returnedString = auto.SendToOtherApp(); // the string filled at the modal form text boxed will be returned to this variable
}
}
THE FORM THAT WILL BE SHOWED AS MODAL FORM
namespace Auto
{
public partial class NAVForm : Form
{
public NAVForm()
{
InitializeComponent();
}
public string UseThis(string txt)
{
if (txt.Trim().Length != 0)
{
return txt;
}
else
{
return "didn't work";
}
}
private void button1_Click(object sender, EventArgs e)
{
UseThis(textBox1.Text);
}
}
}
Please note that the access modifier of textBox1 at NAVForm should be set to public in order for it to be visible to class Auto
Let me know if i misunderstood something to correct it.

Returning a String from a C# WinForm .dll

I have a C# .dll that is envoked from within a C# application useing "System.Reflection" at runtime. The .dll contains a WinForm class which is used to display information to the user. The .dll is envoked using the following code:
DLL = Assembly.LoadFrom(strDllPath);
classType = DLL.GetType(String.Format("{0}.{0}", strNsCn));
classInst = Activator.CreateInstance(classType, paramObj);
Form dllWinForm = (Form)classInst;
dllWinForm.ShowDialog();
Now, my problem is now that I want to return a string from the WinForm .dll. This could be an error or just to show that the process completed correctly. I know how this is done when calling a method from within a requested .dll, as follows:
System.Reflection.Assembly LoadedAssembly = System.Reflection.Assembly.Load("mscorlib.dll");
System.Console.WriteLine(LoadedAssembly.GetName());
object myObject = LoadedAssembly.CreateInstance("System.DateTime", false, BindingFlags.ExactBinding, null, new Object[] {2000, 1, 1, 12, 0, 0}, null, null);
MethodInfo m = LoadedAssembly.GetType("System.DateTime").GetMethod("ToLongDateString");
string result = (string) m.Invoke(myObject, null);
but how do you do this for my case of a WinForm called from a .dll at runtime?
Any suggestions would be most appreciated.
Okay, so to boil the question and the comments down, what we're trying to do here is have a C# app that loads a dll that was implemented by a 3rd party at a later date, and the app needs to get some status information from the component in the loaded dll (the fact that the component uses WinForms vs. some other UI seems completely inconsequential).
The best way to do it is to start out with an interface or base class that can be shared between the hosting application and the loaded component. In order to achieve this, the interface needs to be in a separate dll. So first we create a class library project and add the following class:
using System;
using System.Windows.Forms;
namespace SimplePluginShared
{
public class PluginBase : Form
{
public virtual String GetStatus()
{
return null;
}
}
}
Then add a reference to that class library from the project that implements the component you're loading via reflection (or share it with your third party for them to implement). Here is an example implementation of Plugin Base:
using System;
using System.Windows.Forms;
using SimplePluginShared;
namespace SimplePluginExample
{
public partial class MyForm : PluginBase
{
private String _status = "Unspecified";
public MyForm()
{
InitializeComponent();
}
public override string GetStatus()
{
return _status;
}
private void btnGive_Click(Object sender, EventArgs e)
{
_status = "Give Him The Stick.";
this.DialogResult = DialogResult.OK;
this.Close();
}
private void btnDontGive_Click(object sender, EventArgs e)
{
_status = "Don't Give Him The Stick!";
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
}
And lastly the code to load and call the component:
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using SimplePluginShared;
namespace SimplePluginHost
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnBrowse_Click(Object sender, EventArgs e)
{
OpenFileDialog openPluginDlg = new OpenFileDialog() { DefaultExt = "dll", Multiselect = false, Title = "Open Plugin DLL", Filter = "DLLs|*.dll" };
if (openPluginDlg.ShowDialog() == DialogResult.OK)
{
txtPluginPath.Text = openPluginDlg.FileName;
}
}
private void btnGo_Click(Object sender, EventArgs e)
{
Assembly pluginDll = Assembly.LoadFrom(txtPluginPath.Text);
Type pluginType = pluginDll.GetTypes().Where(t => typeof(PluginBase).IsAssignableFrom(t)).First();
PluginBase pluginInstance = (PluginBase)Activator.CreateInstance(pluginType);
pluginInstance.ShowDialog();
MessageBox.Show(pluginInstance.GetStatus());
}
}
}
He are some screenshots:
Why can't you add the dll as a reference to your project and call it that way? (Just like any other assembly?)

Categories