I'm creating an add-in for Autodesk Inventor. Basically, you define the buttons you want to add, and tell the app to add the button definitions. The problem I'm having is that when I define the 'OnExecute' method for the button definition, the button doesn't execute. I think the way I'm trying to organize the code is what's creating the problem.
I have a CustomButton class that has a delegate property that looks like this (the signature is void with an input of a NameValueMap Interface)
public class CustomButton
{
// … properties and methods that don't matter here
public ButtonDefinitionSink_OnExecuteEventHandler Execute { get; set; }
}
In the main Activate() method (what's called when Inventor starts) I create an instance of the following class to set all the button definitions and the methods that fire when they are clicked. That class looks like this:
public class CustomButtonDefinitions
{
public CustomButtonDefinitions(ref Application app)
{
_inventorApp = app;
InitializeButtonDefinitions();
}
public List<CustomButton> CustomButtons { get; set; } = new List<CustomButton>();
private void InitializeButtonDefinitions()
{
AddTestButton();
}
private void AddTestButton()
{
var testButton = new CustomButton
{
DisplayName = "test",
InternalName = "testCommand1",
Ribbon = "Assembly",
RibbonPanel = "Simplification",
IconSource = "./Assets/test.jpg",
Classification = CommandTypesEnum.kFileOperationsCmdType,
ShowText = true,
UseLargeIcon = true,
};
testButton.Execute = TestButton_Execute;
CustomButtons.Add(testButton);
}
private void TestButton_Execute(NameValueMap Context)
{
// This is where the logic of the button would go.
// For now, just something that gives me an indication it worked.
System.Windows.Forms.MessageBox.Show("Hello");
_inventorApp.ActiveDocument.Close();
}
}
Where I think the source of the error comes from is the next code (this is in the Activate():
CustomButtonDefinitions customButtonDefinitions = new CustomButtonDefinitions(ref _InventorApp);
foreach (var button in customButtonDefinitions.CustomButtons)
{
// this creates the button in Inventor
var buttonDef = button.CreateButtonDefinition(ref controlDefs);
// and this subscribes the button click event to my method
buttonDef.OnExecute += button.Execute;
}
There must be something un-subscribing my method from the button click event.
I'll be posting this in the Inventor forums as well, but wanted to check here too since I'm new to delegates and event handlers. I'm either not understanding something about delegates/events or it's something Inventor specific that I'll need some other help with.
Hopefully this is enough to give some context. Thanks in advance.
The problem was the fact that I wasn't creating the button definition in a high enough scope. I needed to create a variable above the scope of the Activate() method so the app could see it when needed.
Related
I am trying to develop a program in which it could create forms and add controls to it at runtime.
It also should be able to save, (Open and Edit) the forms created with the new controls added it at Runtime.The Application starts In the Main form.
CODE BEHIND MAIN Form
private void Btn_CREATE_FORM_Click(object sender, EventArgs e)
{
Form_Properties fp = new Form_Properties();
fp.Show();
}
private void BTn_ADD_BTN_Click(object sender, EventArgs e)
{
/// WHAT CODE SHOULD I ENTER TO ADD BUTON TO NEW FORM
}
Basically the main form is used to create/open/save new forms and add controls to it.
When the user clicks on Create New Form button the user will be presented with the following form (FORM_PROPERTIES) in which the user can customize the name, width and height of the new form.
CODE BEHIND FORM_PROPERTIES Form
public partial class Form_Properties : Form
{
public Form_Properties()
{
InitializeComponent();
}
String form_name;
int form_width;
int form_height;
private void Btn_OK_Click(object sender, EventArgs e)
{
form_name = TBox_NAME.Text;
form_width = Convert.ToInt32(TBox_WIDTH.Text);
form_height = Convert.ToInt32(TBox_HEIGHT.Text);
New_Form nf = new New_Form();
nf.Text = form_name;
nf.Width = form_width;
nf.Height = form_height;
nf.Show();
}
}
The following image shows what happens at runtime based on the code I have written so far.
ISSUES
Need help to Write Code
To add controls to new form created.
To Save/Open/Edit Functionalities.
I also need to know the method to access properties of added controls at runtime.
eg: If the user adds a text box to the NEW FORM and decides to type some text in it, I need a method to save that text.
Is there a way for me to name the added controls?
It seems you want to build some kind of WinForms' form designer. Your program would be similar to Glade (though Glade is much more powerful).
I'm afraid the question is too broad, though. There are many questions to answer, for example, how do you describe the created interface.
While Glade uses XML, you can choose another format, such as JSON. Let's say that you have a TextBox with the word "example" inside it.
{ type:"textbox" text:"example" }
It seems you want to add your components to the form as in a stack. Maybe you could add its position. For example, a form containing a label
("data"), a textbox ("example"), and a button ("ok"), would be:
{
{ pos:0, type:"label", text:"data" },
{ pos:1, type:"textbox", text:"example" },
{ pos:2, type:"button", text:"ok" },
}
But this is just a representation. You need to a) store this when the form is saved, and b) load it back when the form is loaded.
For that, you will need a class representing the components, such as:
public class Component {
public override string ToString()
{
return string.Format( "position:{0}, text:{1}", this.Position, this.Text );
}
public int Position { get; set; }
public string Text { get; set; }
}
public class TextBoxComponent: Component {
public override string ToString()
{
return base.ToString() + "type:\"textbox\"";
}
}
...and so on. This is a big task, I'm afraid, with no simple answer.
I have the following static method that adds a selected product into an order.
public static void addToOrderFromPicture(string product, string qty, string _price)
{
//I've cut the code as it's not important to the question.
order.Add(product);
}
The products are displayed as Controls on a flow layout panel. The user will click the 'Add to Order' button on the control. This activates the following method.
private void btn_add_Click(object sender, EventArgs e)
{
if (Main.sessionInProgress == true)
{
OrderQty qty = new OrderQty(lbl_caseSize.Text.ToString(), lbl_wholesale.Text.ToString(), lbl_product.Text, lbl_volume.Text.ToString(), lbl_physical.Text, lbl_available.Text, lbl_oo.Text, lbl_inner.Text, lbl_pltQty.Text, lbl_repeat.Text);
qty.StartPosition = FormStartPosition.CenterParent;
DialogResult result = qty.ShowDialog();
if (result == DialogResult.Yes)
{
if (Main.roundCheck == true)
{
// MessageBox.Show(qty.qtyReturn.ToString());
qty.qtyReturn = autoRoundToCaseSize(qty.qtyReturn);
//MessageBox.Show(qty.qtyReturn.ToString());
Main.addToOrderFromPicture(lbl_product.Text.ToString(), qty.qtyReturn.ToString(), qty.priceReturn.ToString());
}
else
{
Main.addToOrderFromPicture(lbl_product.Text.ToString(), qty.qtyReturn.ToString(), qty.priceReturn.ToString());
}
btn_add.Text = "X";
btn_add.BackColor = Color.FromArgb(236, 112, 99);
}
}
}
The reason for the main function being static is so I can call it from this method. In Swift I would use a delegate to pass data between forms etc. However, I'm unsure on how to do this within C#.
Is there a similar method for passing data between forms as there is in Swift. How would I go about doing it? Or is there a way for me to call the method in Main without the need for it to be static.
EDIT: I don't think I've explained the forms etc very well so will try clear it up.
The addToOrderFromPicture method is contained within Main. This function adds products to the order list which is also static and contained within Main.
The 'btn_add_Click' method is contained in Product.cs which is a UserControl. This user control is displayed on a flowPanel which sits on the main form.
So the user clicks activates a function on Product.cs, this creates an instance of OrderQty.cs which is returns a qty to Product.cs - From Product.cs the addToOrder method within Main is called and the data like qty etc is passed to it.
So Product.cs -> OrderQty.cs -> Product.cs -> Main.cs
Your form is still accessible after you've called this.Close() and ShowDialog has returned, so you can do this:
OrderQty qty = new OrderQty(lbl_caseSize.Text.ToString(), lbl_wholesale.Text.ToString(), lbl_product.Text, lbl_volume.Text.ToString(), lbl_physical.Text, lbl_available.Text, lbl_oo.Text, lbl_inner.Text, lbl_pltQty.Text, lbl_repeat.Text);
qty.StartPosition = FormStartPosition.CenterParent;
DialogResult result = qty.ShowDialog();
if (result == DialogResult.Yes)
{
qty.addToOrderFromPicture(lbl_product.Text.ToString(), qty.qtyReturn.ToString(), qty.priceReturn.ToString());
}
Maybe you can keep your logic in a separate class instead of Main.
public class Service
{
public List<Product> Order { get; set; }
public void addToOrderFromPicture(string product, string qty, string _price)
{
Order.Add(product);
}
}
3 methods to do this.
First as the main window is created once use singleton pattern on it, declare a public static instance of form and access public methods,
Second pass a reference of main window to usercontrol and use its public methods.
Third add an event to usercontrol and hook handler in main window, whenever user clicks button, fire an event.
I'm writing a program for managing a tool inventory and have run into a problem when I have the users mark a tool as 'fixed'.
The program should work as follows:
Using TIView, TIViewModel, TIModel:
Employee checks tool out.
Tool happens to get damaged during use.
Employee return's the tool marking it as damaged and reporting the problem.
The tool is marked as returned and locked from being check out until fixed.
Using VPRView, VPRViewModel, and VPRModel:
An inspector goes into a data grid showing all tools with problems.
The inspector corrects the problem, marks the tool as fixed, then submits the data.
The program updates the SQLite database with the inspectors ID number, their solution, marks the problem as fixed and logs the date/time of completion.
THE PROBLEM STEP:
8. The program then runs the PopulateToolInventory method from the TIViewModel to update the inventory list so that the tool is no longer locked.
Summarized:
When the inspector marks the tool as fixed the database is updated using the VPRView, VPRViewModel, and VPRModel. The method to pull the data for the tool inventory is found in the TIViewModel. How do I get the application to execute the 'PopulateToolInventory' method from the VPRViewModel after uploading the data to the database via the VPRViewModel?
Code Sample:
VPRViewModel:
public void SubmitSolution()
{
VPRModel vprm = new VPRModel();
vprm.SubmitProblemSolution(ProblemSolved, ProblemSolution, InspectorID, SelectedReport[0].ToString());
ProblemReports = vprm.RetrieveProblemReports();
InspectorID = null;
ProblemSolution = null;
ProblemSolved = false;
MessageBox.Show("Solution successfully recorded!", "Success!", MessageBoxButton.OK);
// This is where I would want to call the method from the TIViewModel to update the data grid on the TIView.
}
TIViewModel:
private DataTable _toolInventory;
public DataTable ToolInventory
{
get { return _toolInventory; }
set
{
_toolInventory = value;
NotifyOfPropertyChange(() => ToolInventory);
}
}
public void PopulateToolInventory()
{
TIModel tim = new TIModel();
ToolInventory = tim.RetrieveToolInventory();
}
ShellViewModel:
class ShellViewModel : Conductor<object>
{
public void Open_ToolInventory()
{
ActivateItem(new TIViewModel());
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
VPRViewModel vprvm = new VPRViewModel();
wm.ShowDialog(vprvm);
}
}
FYI: I'm using Caliburn.Micro if this helps with any solution.
Hopefully this is enough information. If not, just ask for what you need! Also, please don't eat my code alive. I'm self taught and know that I'm far from a professional developer but this is a passion of mine and I'm really enjoying it. Constructive criticism is appreciated, just please don't make me feel stupid.
Using Ed's idea in the question comments I did the following.
class ShellViewModel : Conductor<object>
{
public void Open_ToolInventory()
{
ActivateItem(new TIViewModel());
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
VPRViewModel vprvm = new VPRViewModel();
wm.ShowDialog(vprvm);
}
}
Was changed to:
class ShellViewModel : Conductor<object>
{
TIViewModel tivm = new TIViewModel();
VPRViewModel vprvm = new VPRViewModel();
public void OpenToolInventory()
{
ActivateItem(tivm);
}
public void ViewProblemReport()
{
WindowManager wm = new WindowManager();
wm.ShowDialog(vprvm);
tivm.PopulateToolInventory();
}
}
This runs the targeted method after the dialog is closed updating the tool inventory to reflect all the solved problems at once. You're the best, Ed!
I am newbie in desktop application programs and I want to create an applications which will use only 1 window for all things. First of all I create a simple LOGIN form with "Login" button and 2 text fields. When user successfully logins window changes it's form. I pinned a picture of the, lets say, MAIN form, where user can switch between "screens" (I don't know how to properly name it) by clicking 1..4 buttons at the right. And we have the top panel with profile photo and nickname which persist all the time program still in the MAIN form.
So, the main question is: HOW should I do it? Which widgets I should use and how does dynamic content in GTK+ (and others, like Qt etc.) should be implemented? So I want answers, links to topics which covers this situation, guides and lessons. Anything will help.
I read a lot of documentation but it seems that I can't find proper information just because I don't know how to formulate my problem for search query.
Thank you, I hope I'll find some help.
P.S. I am gonna use C# and GTK# for this application.
You only need to create various boxes (Gtk.VBox or Gtk.HBox), and hide/show them as you need to. I did not know about Gtk.Stack, but I bet it uses the same principle, maybe with some optimization.
class UniqueWindowView: Gtk.Window
{
void Build()
{
var vbMainBox = new Gtk.VBox();
this.vbLoginPage = this.BuildLoginPage();
this.vbNotebook = this.BuildNotebook();
vbMainBox.PackStart( this.vbLoginPage, true, true, 100 );
vbMainBox.PackStart( this.vbNotebook, true, true, 5 );
vbMainBox.Show();
this.Add( vbMainBox );
this.SetSizeRequest( 600, 400 );
this.Show();
}
// ...
public Gtk.VBox vbLoginPage {
get; private set;
}
public Gtk.VBox vbNotebook {
get; private set;
}
public Gtk.Notebook nbNotebook {
get; private set;
}
public Gtk.Button btLogin {
get; private set;
}
}
Once you have this scheme running, you have to prepare to show or hide the appropriate boxes.
class UniqueWindowCtrl
{
public UniqueWindowCtrl()
{
this.view = new UniqueWindowView();
this.view.DeleteEvent += (o, args) => this.Quit();
this.view.btLogin.Clicked += (sender, e) => this.ShowNotebook();
}
public void Start()
{
this.ShowLogin();
}
public void ShowLogin()
{
this.view.vbLoginPage.Show();
this.view.nbNotebook.Hide();
}
public void ShowNotebook()
{
this.view.vbLoginPage.Hide();
this.view.nbNotebook.Show();
}
void Quit()
{
this.view.Hide();
Gtk.Application.Quit();
}
UniqueWindowView view;
}
You can find the whole source code for a unique window in GTK# here.
Hope this helps.
I suggest you take a look at GtkStack. This will enable you to have multiple widget layouts in a single window and provides you with an easy way to change which widgets are shown. GtkStack is commonly used with StackSwitcher. However normal buttons will work just as well. Can I also suggest you use Glade to lay out your widgets? This will give you a presentation of how your widgets sets will look.
Edit: GtkStack does not appear to be available for Gtk#.
I hope you guys can help me out as I can't find anything useful that helps with the understanding of my problem:
I'm trying to realize a passive MVP approach on my C# WinForms application which has list views and corresponding detail views.
So far I've got the following structure (pseudo code):
ListPresenter(new Repository(), new ListView(), new DetailPresenter(new DetailView());
Implementation:
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter(IDetailView detailView)
{
_view = detailView;
}
public void Show(IUser user)
{
InitializeView(user);
_view.Show();
}
}
public class UserListPresenter
{
//private members (_userRepo, _listView, _detailPresenter)
public UserListView(IUserRepository userRepo, IListView listView, IDetailPresenter detailPresenter)
{
//wire up private members..
_listView.EditCommandFired += this.ShowEditForm;
}
private void OnListViewEditCommandFired(object sender, EventArgs args)
{
_detailPresenter.LoadUser(_listView.SelectedUser);
_detailPresenter.Show(); //modal
}
}
public class UserListForm : Form, IUserListView
{
public event EventHandler EditCommandFired;
public IUser SelectedUser { get { return gridView.FocusedRowHandle as IUser; } }
public void LoadUsers(List<IUser> users)
{
gridView.DataSource = users;
}
// other UI stuff
}
My problem is: I can only show the edit form once. As soon as I try to open it for a second time my View (the form) is disposed (System.ObjectDisposedException).
How do I fix that? Do I have the wrong approach here? Do I either cancel the form's close and just hide it and trust the garbage collector to collect it once the DetailPresenter is disposed? Do I create (new() up) a new presenter each time the Edit event is fired? I would then have to introduce some kind of factory as I somehow lose dependency injection. I'd appreaciate if someone could point out how the best practice in this case would look like and what I may be doing wrong here..
I was doing Winforms MVP a while ago so not sure if I can help, but the case my be as follows.
In my approach, the view was owning presenter, pseudo code:
MyForm form = new MyForm(new PresenterX);
form.Show(); //or showdialog
In this case instance is still there after closing.
In your case since presenter owns the view, its possible that once presenter is not used, GC disposes presenter and contained view.
Or even if presenter is still in use, since view is private GC may collect it once closed.
Try to debug in Release mode and see what happens with closed form instance.
EDIT:
Other idea is:
Create instance of view first and then pass to presenter
So approach that may fail (I don' see full code so guessing)
UserDetailPresenter p = new UserDetailPresenter(new YourView());
Try
YourForm view = new YourForm(); //as global variable, view should be reusable anyway
Somewhere in code
UserDetailPresenter p = new UserDetailPresenter(view);
p.Show(userInstance);
You're using one instance of DetailPresenter to show details for different objects. So you'll have to initialize the view of the DetailPresenter each time you want to show it, in your current implementation. This could be one way of doing it, the ListPresenter can inject a new instance of DetailsView everytime it asks the DetailPresenter to show it.
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter()
{
}
public void Show(IUser user, IDetailView detailView)
{
_view = detailView;
InitializeView(user);
_view.Show();
}
}
Or another cleaner way could be some sort of ViewFactory to get a new instance of the view before showing it.
private IDetailViewFactory _detailViewFactory;
public UserDetailPresenter(IDetailViewFactory detailViewFactory)
{
_detailViewFactory = detailViewFactory;
}
public void Show(IUser user )
{
_view = _detailViewFactory.Resolve();//Some method to get a new view
InitializeView(user);
_view.Show();
}
But if you want to do it a bit differently, this is more passive view way.
In the ListPresenter:
private void OnListViewEditCommandFired(object sender, EventArgs args)
{
_listView.Show(_listView.SelectedUser);//tells view to show another view
}
In the ListView:
public ListView()
{
new ListPresenter(this); // initializes presenter
}
public void Show(IUser user)
{
new DetailsView(user); // creates a new view
}
In the DetailsView:
public DetailsView(IUser user)
{
new DetailsPresenter(this, user); //creates presenter
}
Finally:
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter(IDetailView detailView, IUser user)
{
_view = detailView;
LoadUser(user);
_view.SomeProperty = _userData;//to populate view with data
_view.Show(); // tells the view to show data
}
}