AddHandler CommandWillStart/RemoveHandler CommandWillStart not working

AddHandler CommandWillStart/RemoveHandler CommandWillStart not working

JFN_KSH
Advocate Advocate
1,870 Views
11 Replies
Message 1 of 12

AddHandler CommandWillStart/RemoveHandler CommandWillStart not working

JFN_KSH
Advocate
Advocate

In my form I have a "OFF" button which fire's off the "RemoveHandler" part. Even after triggering the "RemoveHandler" the program is still issuing the "CommandWillStart" event.  Here's my (fairly simple) problematic code I had written so far. I've chopped down most of the code that does not relate to the eventhandler. Is there any inconsistency concerning the "RemoveHandler" function?
You're help is much appreciated... Thanks!

Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.ApplicationServices.Core.Application
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.ApplicationServices.Application
Imports System.Windows.Forms
Imports Autodesk.AutoCAD.Colors
Imports System.IO
Imports Autodesk.AutoCAD.Runtime

Public Class Form1
    Implements IExtensionApplication
    Private CurrentLayerName As String
    Private _SelectedLayer As LayerData
    Private Shared acDoc As Document

    Public Sub Initialize() Implements IExtensionApplication.Initialize
            Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog("Initialize")
        acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument
    End Sub
	
    Public Sub Terminate() Implements IExtensionApplication.Terminate
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        _SelectedLayer = New LayerData
      End Sub

    Private Sub myOnButton_Click(ByVal sender As Object, ByVal e As EventArgs) Handles myOnButton.Click
         AddHandler acDoc.CommandWillStart, AddressOf SwapTheLayer
    End Sub
	
    Private Sub OFF_Button_Click(ByVal sender As Object, ByVal e As EventArgs) Handles OFF_Button.Click
        RemoveHandler acDoc.CommandWillStart, AddressOf SwapTheLayer
    End Sub

    Private Sub SwapTheLayer(ByVal senderObj As Object, ByVal e As CommandEventArgs)
        CurrentLayerName = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("CLAYER")
        Dim importedLayer As Array = _SelectedLayer.ToArray
        Select Case e.GlobalCommandName
            Case "MTEXT"
                If checkIfLayerExistInDrawing(MText_TextBox.Text) = True Then
                    Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog("layer exist")
                    Autodesk.AutoCAD.ApplicationServices.Application.SetSystemVariable("CLAYER", MText_TextBox.Text.ToString)
                Else
                    Dim myCreateLay As New CreateLayer(importedLayer(0).MyLayerName,
                                                       importedLayer(0).MyLayerDescription,
                                                       importedLayer(0).MyLayerColor,
                                                       importedLayer(0).MyLayerPlot,
                                                       importedLayer(0).MyLayerLinetypeName,
                                                       importedLayer(0).MyLayerLineweight)
                    Autodesk.AutoCAD.ApplicationServices.Core.Application.SetSystemVariable("CLAYER", MText_TextBox.Text)
                    Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog("layer do not exist in drawing")
                End If
                Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog("AddHandler CommandEnded")
                AddHandler acDoc.CommandEnded, AddressOf BackToOriginalLayer
                AddHandler acDoc.CommandCancelled, AddressOf BackToOriginalLayer
                AddHandler acDoc.CommandFailed, AddressOf BackToOriginalLayer
            End Select
    End Sub
    Private Sub BackToOriginalLayer(ByVal senderObj As Object, ByVal e As CommandEventArgs)
        Select Case e.GlobalCommandName
            Case Is = "MTEXT"
                Autodesk.AutoCAD.ApplicationServices.Application.SetSystemVariable("CLAYER", CurrentLayerName)
                Autodesk.AutoCAD.ApplicationServices.Application.ShowAlertDialog("RemoveHandler CommandEnded")
                RemoveHandler acDoc.CommandEnded, AddressOf BackToOriginalLayer
                RemoveHandler acDoc.CommandCancelled, AddressOf BackToOriginalLayer
                RemoveHandler acDoc.CommandFailed, AddressOf BackToOriginalLayer
        End Select
    End Sub

    Private Function checkIfLayerExistInDrawing(ByVal myLayerName As String)
        Dim LayerExist As Boolean
        '' Get the current document and database
        Dim acCurDb As Database = acDoc.Database
        '' Start a transaction
        Using acTrans As Transaction = acCurDb.TransactionManager.StartTransaction()
            '' Open the Layer table for read
            Dim acLyrTbl As LayerTable
            acLyrTbl = acTrans.GetObject(acCurDb.LayerTableId, OpenMode.ForRead)

            'layer exist in drawing...
            If acLyrTbl.Has(myLayerName) = False Then
                LayerExist = False
            Else
                LayerExist = True
            End If
        End Using
        Return LayerExist
    End Function

End Class
0 Likes
1,871 Views
11 Replies
Replies (11)
Message 2 of 12

_gile
Consultant
Consultant

Hi,

 

It's difficult to give you a better reply than Norman's did here and it looks like you didn't take any account of its advices...

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 3 of 12

JFN_KSH
Advocate
Advocate

Actually I did tried almost all of what he suggested without success... (change my share variable & all) I will separate the buisness logic from UI element (even if I douth this could have any effect on the handler...). And he did not replied yet to my last interrogation... Was hoping to get more cues on this forum... Thanks for your input... Appreciated...

 

0 Likes
Message 4 of 12

_gile
Consultant
Consultant

You certainly tried Norman's suggestions, but didn't apply them to the code you give here (which seems to be the same as the one at TheSwamp).

 

You keep the acDoc field static (Shared) and set it in the Initialize() method which is really not a good practice because static members have a 'global' (per session) scope when you need a 'per document' scope (almost for MdiActiveDocument). You can read this thread about this.

 

You still do not show how the UI is shown in AutoCAD: is it a modeless dialog (per session) or a modal dialog (per document) ?

 

Here's a little example which uses static (Shared) members where needed and the Document.UserData HashTable for a 'per document' sentinel to know if the event is handled or not (you can see this thread about UserData).

 

The UI part:

 

using System;
using System.Windows.Forms;

namespace CommandHandling
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public static bool IsHandled { get; set; }

        private void buttonOn_Click(object sender, EventArgs e)
        {
            IsHandled = true;
            Commands.AddHandler();
        }

        private void buttonOff_Click(object sender, EventArgs e)
        {
            IsHandled = false;
            Commands.RemoveHandler();
        }
    }
}

The AutoCAD stuff part:

 

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(CommandHandling.Commands))]
[assembly: ExtensionApplication(typeof(CommandHandling.Commands))]

namespace CommandHandling
{
    public class Commands : IExtensionApplication
    {
        static DocumentCollection docs = Application.DocumentManager;

        // Shows the dialog as modeless
        [CommandMethod("Test")]
        public static void Test()
        {
            Form1 dialog = new Form1();
            Application.ShowModelessDialog(dialog);
        }

        // Adds the handler if not already added
        public static void AddHandler()
        {
            Document doc = docs.MdiActiveDocument;
            if (doc == null)
                return;
            if (doc.UserData["commandHandled"] == null || !(bool)doc.UserData["commandHandled"])
                doc.CommandWillStart += Doc_CommandWillStart;
            doc.UserData["commandHandled"] = true;
        }

        // Removes the handler if already added
        public static void RemoveHandler()
        {
            Document doc = docs.MdiActiveDocument;
            if (doc == null)
                return;
            if (doc.UserData["commandHandled"] != null && (bool)doc.UserData["commandHandled"])
                doc.CommandWillStart -= Doc_CommandWillStart;
            doc.UserData["commandHandled"] = false;
        }

        // Initialization
        public void Initialize()
        {
            Form1.IsHandled = true;
            AddHandler();
            docs.DocumentActivated += Docs_DocumentActivated;
        }

        public void Terminate()
        { }

        // CommandWillStart handler (just an alert box for testing)
        private static void Doc_CommandWillStart(object sender, CommandEventArgs e)
        {
            Application.ShowAlertDialog(e.GlobalCommandName);
        }

        // Document activated handler
        private void Docs_DocumentActivated(object sender, DocumentCollectionEventArgs e)
        {
            if (Form1.IsHandled)
                AddHandler();
            else
                RemoveHandler();
        }
    }
}

 



Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 5 of 12

JFN_KSH
Advocate
Advocate

Thanks Gile! i will try to play with this tonight... My Ui is called using modal dialog:

 

Public Class CallToForm
    <CommandMethod("ShowForm")>
    Public Sub ShowForm()
        Dim MetForm As New Form1
        Application.ShowModalDialog(New Form1, MetForm, True)
    End Sub
End Class

I'm new to .NET, I used to do LISP... I know I have a lot to learn & appreciated all the help you have given me so far. Merci beaucoup!

0 Likes
Message 6 of 12

_gile
Consultant
Consultant

If you're using a modal dialog, it's much more simple because you don't have to deal with multiple document.

 

UI part:

 

using System;
using System.Windows.Forms;

namespace PerDocumentCommandHandling
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public bool IsHandled { get; private set; }

        private void buttonOn_Click(object sender, EventArgs e)
        {
            IsHandled = true;
            DialogResult = DialogResult.OK;
        }

        private void buttonOff_Click(object sender, EventArgs e)
        {
            IsHandled = false;
            DialogResult = DialogResult.OK;
        }
    }
}

 

 

AutoCAD part

 

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(PerDocumentCommandHandling.Commands))]

namespace PerDocumentCommandHandling
{
    public class Commands
    {
        // per document field
        private bool isHandled;

        [CommandMethod("Test")]
        public void Test()
        {
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Form1 dialog = new Form1();
            Application.ShowModalDialog(dialog);
            if (dialog.IsHandled)
            {
                if (!isHandled)
                    doc.CommandWillStart += Doc_CommandWillStart;
                isHandled = true;
            }
            else
            {
                if (isHandled)
                    doc.CommandWillStart -= Doc_CommandWillStart;
                isHandled = false;
            }
        }

        private void Doc_CommandWillStart(object sender, CommandEventArgs e)
        {
            Application.ShowAlertDialog(e.GlobalCommandName);
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

Message 7 of 12

JFN_KSH
Advocate
Advocate

So I've implemented your code related to UserData. Ultimately I would like my handlers to be MDI (Multiple Document Interface) so I choose this option. Everything works fine using your default testing code (many thanks!). My first step was to separate AutoCAD buisness logic from my Form1 (UI). Now i'm running into some trouble because i can't reference any textbox on my Form1 from my new Class (Autocad buisness logic). I tried to change my Form1 to the same namespace as you show it but all of my Form design dissapear from the designer interface... I also tried to change the texbox to public (by default it was Friend WithEvents) with no sucess (reference to a non-shared member require an object reference) . Now I know this is another topic but do I have to define some Public Shared GET & SET for every Textbox on my form? Or should I edit the Form1.Designer file & set my textbox to Public Shared WithEvents? Any idea on how I should takle this?

0 Likes
Message 8 of 12

_gile
Consultant
Consultant

Hi,

 

You have to understand, the accessibility levels (private, internal, public) on one hand and the differences between static and instance members on the other hand.

 

Here's a better implementation of the modeless dialog including a TextBox.

 

On the UI side, there're only instance members (the properties could have been set 'internal' as there're only called within the same assembly).

 

using System;
using System.Windows.Forms;

namespace PerDocumentCommandHandling
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public bool IsHandled { get; set; }

        public string TextValue
        {
            get { return textBox1.Text; }
        }

        private void buttonOn_Click(object sender, EventArgs e)
        {
            IsHandled = true;
            Commands.AddHandler();
        }

        private void buttonOff_Click(object sender, EventArgs e)
        {
            IsHandled = false;
            Commands.RemoveHandler();
        }
    }
}

On the AutoCAD buisness side, we use two static (Shared) fields so that they're 'per session' scope.

The DocumentCollection (docs) is intantiated in the Initialize method at startup and the Form1 (dialog) is instantiated at the first call of the Test command.

Elsewhere in this class, we can call the dialog instance members.

 

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(PerDocumentCommandHandling.Commands))]
[assembly: ExtensionApplication(typeof(PerDocumentCommandHandling.Commands))]

namespace PerDocumentCommandHandling
{
    public class Commands : IExtensionApplication
    {
        // per session fields
        static DocumentCollection docs;
        static Form1 dialog;

        // Initialization
        public void Initialize()
        {
            docs = Application.DocumentManager;
            docs.DocumentActivated += Docs_DocumentActivated;
        }

        public void Terminate()
        { }


        // Shows the dialog as modeless
        [CommandMethod("Test")]
        public static void Test()
        {
            if (dialog == null)
                dialog = new Form1();
            Application.ShowModelessDialog(dialog);
        }

        // Adds the handler if not already added
        public static void AddHandler()
        {
            Document doc = docs.MdiActiveDocument;
            if (doc == null)
                return;
            if (doc.UserData["commandHandled"] == null || !(bool)doc.UserData["commandHandled"])
                doc.CommandWillStart += Doc_CommandWillStart;
            doc.UserData["commandHandled"] = true;
        }

        // Removes the handler if already added
        public static void RemoveHandler()
        {
            Document doc = docs.MdiActiveDocument;
            if (doc == null)
                return;
            if (doc.UserData["commandHandled"] != null && (bool)doc.UserData["commandHandled"])
                doc.CommandWillStart -= Doc_CommandWillStart;
            doc.UserData["commandHandled"] = false;
        }
        // CommandWillStart handler (just an alert box for testing)
        private static void Doc_CommandWillStart(object sender, CommandEventArgs e)
        {
            Application.ShowAlertDialog(dialog.TextValue);
        }

        // Document activated handler
        private void Docs_DocumentActivated(object sender, DocumentCollectionEventArgs e)
        {
            if (dialog.IsHandled)
                AddHandler();
            else
                RemoveHandler();
        }
    }
}


Gilles Chanteau
Programmation AutoCAD LISP/.NET
GileCAD
GitHub

0 Likes
Message 9 of 12

JFN_KSH
Advocate
Advocate
 
0 Likes
Message 10 of 12

JFN_KSH
Advocate
Advocate

I've played a lot with this & I can't refer to my MText_TextBox from the Autocad side of things. My reference object are always set to an empty string "". On the UI side i've define some Public ReadOnly Property, followed by the constructor, as shown. I could not define a new namespace without loosing all of my design window, so I did not implied this part (please see attachment Form1 for my UI class).

 

 

So, as mentionned, I can't make any reference to my MText_TextBox from the autocad side (SwapTheLayerClass). I always get an empty string (""). Maybe something's wrong in my constructor in the UI ??? 

 

    Public Sub New()
        InitializeComponent()
    End Sub

 

 flabergasted...

0 Likes
Message 11 of 12

fieldguy
Advisor
Advisor

Have you changed the "Modifiers" on your textbox in Form Designer (see image)?

 

If you have a class (dll) that shows / manipulates / uses / requires information from a form (UI), you can change the access to Public in the form designer.  This works for all the form controls I have ever needed.

 

 

0 Likes
Message 12 of 12

JFN_KSH
Advocate
Advocate

thanks for the tip! Yes i've played with that setting quite a lot... I tried different scenario; by default it was set to friend so I changed it to public, I also tried to modify the designer file itself changing the define value of the textbox to Public Shared... no success... Something's wrong in my code... Maybe I'll have to look at the order of event, I'm trying to access the textbox value from a closed winform... 

0 Likes