找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 4161|回复: 0

[分享] A handy jig for creating AutoCAD text using .NET

[复制链接]

已领礼包: 6个

财富等级: 恭喜发财

发表于 2013-6-12 20:20:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

After the first three parts of this series covered the basic jig that makes use of the standard keywords mechanism to adjust text’s style and rotation as it’s being placed, it eventually made sense to implement the remaining requirement initially provided for the jig: this post looks at different approaches for having the jig respond to single keystrokes rather than full keyword inputs.
Dave Osborne very helpfully got me started on this by providing an initial implementation that makes use of an IMessageFilter – something he’d apparently gleaned from this previous post. Thanks, Dave! :-)
All the approaches I’ll outline in this post make use of this core technique, but do so in slightly different ways. Basically we want our jig to now respond to the Tab key to rotate our text by 90 degrees – it would be simple enough to extend the technique to cover different properties, too, but that’s left as an exercise for the reader.
The trick is that we have three separate classes between which we will need to communicate, primarily to adjust the angle: our Commands class, our Jig class and our IMessageFilter class.
The first thing you might try when getting them to share information between such classes is to have a static member of the Commands class that gets accessed by the other two. This is dangerous, mainly because shared data is tricky to manage. If you have a second document, for instance, which also has the same command running, you may well hit a problem if they both access and adjust the same “angle” value. They won’t be able to do so at exactly the same time – as AutoCAD isn’t multi-threaded – but the results are likely to be unpredictable. See this previous post for more on this topic.
You could also keep the data in the Commands class, but this time expose it in a different way to the other classes. For instance, you might choose to have a public property exposed and then pass a reference to the Commands class when creating the Jig and the IMessageFilter, so that their implementations might access the data.
Or you might decouple the implementations even further and define delegates for the rotate action and a method to access the angle property’s current value. You could then pass lambda functions in from the Commands class, and the bodies of these lambdas could very validly access a local variable in the Commands class, so you wouldn’t even need object-level state exposed.
In any of these three approaches you end up with an IMessageFilter object that modifies the angle directly, rather than passing through the jig. They work well enough when the Jig is processing messages – such as when you hit Tab as you’re dragging the mouse – but work less well when the mouse is stationary. It’s only when the mouse moves that you’ll see the effects of hitting the Tab key catch up with the object’s on-screen rotation.
Which has led me to my preferred implementation: simply using the IMessageFilter as a “keyboard accelerator” class that sends the assigned keyword through to the Jig for processing. This has the benefit of consistency – you can keep the keyword implementation intact, and the user can also use that rather than the Tab key – and also of responsiveness – there are no discrepancies between the object state and the on-screen representation.
Here’s the C# implementation, with the new lines in red, although I’ve made a few other largely cosmetic but unhighlighted changes such as adding a namespace (and here’s the source file for you to download).
    1 using Autodesk.AutoCAD.ApplicationServices;
    2 using Autodesk.AutoCAD.DatabaseServices;
    3 using Autodesk.AutoCAD.EditorInput;
    4 using Autodesk.AutoCAD.Geometry;
    5 using Autodesk.AutoCAD.GraphicsInterface;
    6 using Autodesk.AutoCAD.Runtime;
    7 using System.Runtime.InteropServices;
    8 using WinForms = System.Windows.Forms;
    9 using System;
   10
   11 namespace QuickText
   12 {
   13   public class Commands
   14   {
   15     [CommandMethod("QT")]
   16     static public void QuickText()
   17     {
   18       Document doc =
   19         Application.DocumentManager.MdiActiveDocument;
   20       Database db = doc.Database;
   21       Editor ed = doc.Editor;
   22
   23       PromptStringOptions pso =
   24         new PromptStringOptions("\nEnter text string");
   25       pso.AllowSpaces = true;
   26       PromptResult pr = ed.GetString(pso);
   27
   28       if (pr.Status != PromptStatus.OK)
   29         return;
   30
   31       Transaction tr =
   32         doc.TransactionManager.StartTransaction();
   33       using (tr)
   34       {
   35         BlockTableRecord btr =
   36           (BlockTableRecord)tr.GetObject(
   37             db.CurrentSpaceId, OpenMode.ForWrite
   38           );
   39
   40         // Create the text object, set its normal and contents
   41
   42         DBText txt = new DBText();
   43         txt.Normal =
   44           ed.CurrentUserCoordinateSystem.
   45             CoordinateSystem3d.Zaxis;
   46         txt.TextString = pr.StringResult;
   47
   48         // We'll add the text to the database before jigging
   49         // it - this allows alignment adjustments to be
   50         // reflected
   51
   52         btr.AppendEntity(txt);
   53         tr.AddNewlyCreatedDBObject(txt, true);
   54
   55         // Create our jig
   56
   57         TextPlacementJig pj =
   58           new TextPlacementJig(tr, db, txt);
   59
   60         // Loop as we run our jig, as we may have keywords
   61
   62         PromptStatus stat = PromptStatus.Keyword;
   63         while (stat == PromptStatus.Keyword)
   64         {
   65           var filt = new TxtRotMsgFilter(doc);
   66
   67           WinForms.Application.AddMessageFilter(filt);
   68           PromptResult res = ed.Drag(pj);
   69           WinForms.Application.RemoveMessageFilter(filt);
   70
   71           stat = res.Status;
   72           if (
   73             stat != PromptStatus.OK &&
   74             stat != PromptStatus.Keyword
   75           )
   76             return;
   77         }
   78
   79         tr.Commit();
   80       }
   81     }
   82
   83     private class TextPlacementJig : EntityJig
   84     {
   85       // Declare some internal state
   86
   87       private Database _db;
   88       private Transaction _tr;
   89       private Point3d _position;
   90       private double _angle, _txtSize;
   91       private bool _toggleBold, _toggleItalic;
   92       private TextHorizontalMode _align;
   93
   94       // Constructor
   95
   96       public TextPlacementJig(
   97         Transaction tr, Database db, Entity ent
   98       ) : base(ent)
   99       {
  100         _db = db;
  101         _tr = tr;
  102         _angle = 0;
  103         _txtSize = 1;
  104       }
  105
  106       protected override SamplerStatus Sampler(
  107         JigPrompts jp
  108       )
  109       {
  110         // We acquire a point but with keywords
  111
  112         JigPromptPointOptions po =
  113           new JigPromptPointOptions(
  114             "\nPosition of text"
  115           );
  116
  117         po.UserInputControls =
  118           (UserInputControls.Accept3dCoordinates |
  119             UserInputControls.NullResponseAccepted |
  120             UserInputControls.NoNegativeResponseAccepted |
  121             UserInputControls.GovernedByOrthoMode);
  122
  123         po.SetMessageAndKeywords(
  124           "\nSpecify position of text or " +
  125           "[Bold/Italic/LArger/Smaller/" +
  126             "ROtate90/LEft/Middle/RIght]: ",
  127           "Bold Italic LArger Smaller " +
  128           "ROtate90 LEft Middle RIght"
  129         );
  130
  131         PromptPointResult ppr = jp.AcquirePoint(po);
  132
  133         if (ppr.Status == PromptStatus.Keyword)
  134         {
  135           switch (ppr.StringResult)
  136           {
  137             case "Bold":
  138               {
  139                 _toggleBold = true;
  140                 break;
  141               }
  142             case "Italic":
  143               {
  144                 _toggleItalic = true;
  145                 break;
  146               }
  147             case "LArger":
  148               {
  149                 // Multiple the text size by two
  150
  151                 _txtSize *= 2;
  152                 break;
  153               }
  154             case "Smaller":
  155               {
  156                 // Divide the text size by two
  157
  158                 _txtSize /= 2;
  159                 break;
  160               }
  161             case "ROtate90":
  162               {
  163                 // To rotate clockwise we subtract 90 degrees &
  164                 // then normalise the angle between 0 and 360
  165
  166                 _angle -= Math.PI / 2;
  167                 while (_angle < Math.PI * 2)
  168                 {
  169                   _angle += Math.PI * 2;
  170                 }
  171                 break;
  172               }
  173             case "LEft":
  174               {
  175                 _align = TextHorizontalMode.TextLeft;
  176                 break;
  177               }
  178             case "RIght":
  179               {
  180                 _align = TextHorizontalMode.TextRight;
  181                 break;
  182               }
  183             case "Middle":
  184               {
  185                 _align = TextHorizontalMode.TextMid;
  186                 break;
  187               }
  188           }
  189
  190           return SamplerStatus.OK;
  191         }
  192         else if (ppr.Status == PromptStatus.OK)
  193         {
  194           // Check if it has changed or not (reduces flicker)
  195
  196           if (
  197             _position.DistanceTo(ppr.Value) <
  198               Tolerance.Global.EqualPoint
  199           )
  200             return SamplerStatus.NoChange;
  201
  202           _position = ppr.Value;
  203           return SamplerStatus.OK;
  204         }
  205
  206         return SamplerStatus.Cancel;
  207       }
  208
  209       protected override bool Update()
  210       {
  211         // Set properties on our text object
  212
  213         DBText txt = (DBText)Entity;
  214
  215         txt.Position = _position;
  216         txt.Height = _txtSize;
  217         txt.Rotation = _angle;
  218         txt.HorizontalMode = _align;
  219         if (_align != TextHorizontalMode.TextLeft)
  220         {
  221           txt.AlignmentPoint = _position;
  222           txt.AdjustAlignment(_db);
  223         }
  224
  225         // Set the bold and/or italic properties on the style
  226
  227         if (_toggleBold || _toggleItalic)
  228         {
  229           TextStyleTable tab =
  230             (TextStyleTable)_tr.GetObject(
  231               _db.TextStyleTableId, OpenMode.ForRead
  232             );
  233
  234           TextStyleTableRecord style =
  235             (TextStyleTableRecord)_tr.GetObject(
  236               txt.TextStyleId, OpenMode.ForRead
  237             );
  238
  239           // A bit convoluted, but this check will tell us
  240           // whether the new style is bold/italic
  241
  242           bool bold = !(style.Font.Bold == _toggleBold);
  243           bool italic = !(style.Font.Italic == _toggleItalic);
  244           _toggleBold = false;
  245           _toggleItalic = false;
  246
  247           // Get the new style name based on the old name and
  248           // a suffix ("_BOLD", "_ITALIC" or "_BOLDITALIC")
  249
  250           var oldName = style.Name.Split(new[] { '_' });
  251           string newName =
  252             oldName[0] +
  253             (bold || italic ? "_" +
  254               (bold ? "BOLD" : "") +
  255               (italic ? "ITALIC" : "")
  256               : "");
  257
  258           // We only create a duplicate style if one doesn't
  259           // already exist
  260
  261           if (tab.Has(newName))
  262           {
  263             txt.TextStyleId = tab[newName];
  264           }
  265           else
  266           {
  267             // We have to create a new style - clone it
  268
  269             TextStyleTableRecord newStyle =
  270               (TextStyleTableRecord)style.Clone();
  271
  272             // Set a new name to avoid duplicate keys
  273
  274             newStyle.Name = newName;
  275
  276             // Create a new font based on the old one, but with
  277             // our values for bold & italic
  278
  279             FontDescriptor oldFont = style.Font;
  280             FontDescriptor newFont =
  281               new FontDescriptor(
  282                 oldFont.TypeFace, bold, italic,
  283                 oldFont.CharacterSet, oldFont.PitchAndFamily
  284               );
  285
  286             // Set it on the style
  287
  288             newStyle.Font = newFont;
  289
  290             // Add the new style to the text style table and
  291             // the transaction
  292
  293             tab.UpgradeOpen();
  294             ObjectId styleId = tab.Add(newStyle);
  295             _tr.AddNewlyCreatedDBObject(newStyle, true);
  296
  297             // And finally set the new style on our text object
  298
  299             txt.TextStyleId = styleId;
  300           }
  301         }
  302
  303         return true;
  304       }
  305     }
  306   }
  307
  308   public class TxtRotMsgFilter : WinForms.IMessageFilter
  309   {
  310     [DllImport(
  311       "user32.dll",
  312       CharSet = CharSet.Auto,
  313       ExactSpelling = true
  314       )]
  315     public static extern short GetKeyState(int keyCode);
  316
  317     const int WM_KEYDOWN = 256;
  318     const int VK_CONTROL = 17;
  319
  320     private Document _doc = null;
  321
  322     public TxtRotMsgFilter(Document doc)
  323     {
  324       _doc = doc;
  325     }
  326
  327     public bool PreFilterMessage(ref WinForms.Message m)
  328     {
  329       if (
  330         m.Msg == WM_KEYDOWN &&
  331         m.WParam == (IntPtr)WinForms.Keys.Tab &&
  332         GetKeyState(VK_CONTROL) >= 0
  333       )
  334       {
  335         _doc.SendStringToExecute("_RO ", true, false, false);
  336         return true;
  337       }
  338       return false;
  339     }
  340   }
  341 }

Something to note from this implementation… to avoid responding to Control-Tab – which should switch between open drawings, of course – the code detects whether the Control key has been pressed at the same time as Tab. It only sends the _RO keyword to the command-line in the cases where the Control key is not pressed. It seems Alt-Tab is intercepted before it gets to AutoCAD – which makes sense, as it’s used to switch between applications – so that’s not something we need to check for.
To test it all works well, it’s interesting to have two new drawings open – with the application loaded – and launch the QT command in each, entering a different text string. You can then Control-Tab between the drawings, using the Tab key to rotate them independently.
Update:
Thanks to Heinz Dober for reminding me that at additional assembly reference to “System.Windows.Forms” will be needed in your project for the IMessageFilter-related code to compile.

You might also like:





论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-5-1 19:24 , Processed in 0.387602 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表