Monday, July 9, 2018

Change Width of DBText/AttributeReference - Part Three: Real Time Change

In 2 previous posts (here and here), the situation I tried to deal with is to fit a string of text into a limited space, such as a table's field with given width.

Obviously, besides shrinking the text string's width by reducing its WidthFactor, user can also rephrase the word/characters of the text string to reduce the width while keeping the meaning of the text. This way, the text would be presented visually the same (i.e. in the same WidthFactor).

With DBText, since it can be edited in place, so, user can change the wording of the text and see if the text would fit into the space while typing. But the the case of AttributeReference, the user can only either edit it in attribute editing dialog box, or in Properties window. For the former, when the attribute in edited in the dialog box, the real change occurs with the typing, which is good, because you can see if the text's width exceeds out of the allowed space at real time. For the latter, use can only see the change when the editing is completed (the focus leaves the edited field in Properties window).

After doing previous 2 posts, I thought why not doing one more article on changing text/attribute at real time by giving user chances of either changing text string's wording, or WidthFactor. The other reason of writing once more on this topic is that I have not seen sample code on the net about real time change of text entity while typing.

Here is the code of CommandClass RealTimeTextEditor:

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(RealTimeTextEdit.RealTimeTextEditor))]
 
namespace RealTimeTextEdit
{
    public class RealTimeTextEditor
    {
        private Document _dwg;
        private string _defaultTarget = "Text";
        
        public RealTimeTextEditor()
        {
            SelectedId = ObjectId.Null;
            OriginalWidthFactor = 1.0;
            OriginalText = "";
            _dwg = CadApp.DocumentManager.MdiActiveDocument;
        }
 
        #region properties
 
        internal ObjectId SelectedId { private setget; }
        internal string OriginalText { private setget; }
        internal double OriginalWidthFactor { private setget; }
 
        #endregion
 
        #region public methods
 
        [CommandMethod("MyTextEdit")]
        public void EditText()
        {
            while(true)
            {
                var target = SelectEditTarget(_defaultTarget);
                if (!string.IsNullOrEmpty(target))
                {
                    _defaultTarget = target;
 
                    ObjectId textId = ObjectId.Null;
                    if (_defaultTarget=="Text")
                    {
                        textId = SelectTextEntity();
                    }
                    else
                    {
                        textId = SelectAttributeEntity();
                    }
 
                    if (!textId.IsNull)
                    {
                        GetTextInformation(textId);
                        DoEditWork();
                    }
                }
                else
                {
                    break;
                }
            }
 
            _dwg.Editor.WriteMessage("\n");
        }
 
        #endregion
 
        #region private methods
 
        private string SelectEditTarget(string defaultTarget)
        {
            var target = "";
 
            var opt = new PromptKeywordOptions(
                "\nEdit Text or Attribute?");
            opt.AppendKeywordsToMessage = true;
            opt.Keywords.Add("Text");
            opt.Keywords.Add("Attribute");
            opt.Keywords.Add("eXit");
            opt.Keywords.Default = defaultTarget;
 
            var res = _dwg.Editor.GetKeywords(opt);
            if (res.Status== PromptStatus.OK)
            {
                if (res.StringResult!="eXit")
                {
                    return res.StringResult;
                }
            }
 
            return target;
        }
 
        private ObjectId SelectTextEntity()
        {
            ObjectId textId = ObjectId.Null;
 
            var opt = new PromptEntityOptions(
                "\nSelect a TEXT entity:");
            opt.SetRejectMessage(
                "\nInvalid selection: not a TEXT entity!");
            opt.AddAllowedClass(typeof(DBText), true);
 
            var res = _dwg.Editor.GetEntity(opt);
            if (res.Status== PromptStatus.OK)
            {
                textId = res.ObjectId;
            }
 
            return textId;
        }
 
        private ObjectId SelectAttributeEntity()
        {
            ObjectId attId = ObjectId.Null;
 
            while (true)
            {
                var opt = new PromptNestedEntityOptions(
                    "\nSelect an Attribute in block:");
                opt.AllowNone = false;
 
                var res = _dwg.Editor.GetNestedEntity(opt);
                if (res.Status == PromptStatus.OK)
                {
                    if (res.ObjectId.ObjectClass.DxfName.ToUpper() == "ATTRIB")
                    {
                        attId = res.ObjectId;
                    }
                    else
                    {
                        _dwg.Editor.WriteMessage(
                            "\nInvalid selection: not an attribue in block!");
                    }
                }
                else
                {
                    break;
                }
 
                if (!attId.IsNull) break;
            }
 
            return attId;
        }
 
        private void GetTextInformation(ObjectId textId)
        {
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var txt = (DBText)tran.GetObject(textId, OpenMode.ForRead);
                OriginalText = txt.TextString;
                OriginalWidthFactor = txt.WidthFactor;
                SelectedId = textId;
                tran.Commit();
            }
        }
 
        private void DoEditWork()
        {
            using (var dlg = new dlgTextBox(this))
            {
                dlg.TextStringChanged += (o, e) =>
                  {
                      UpdateTextEntity(e.TextString);
                  };
 
                dlg.TextWidthFactorChanged += (o, e) =>
                {
                    UpdateTextEntity(e.WidthFactor);
                };
 
                dlg.Left = 50;
                dlg.Top = 50;
 
                var res = CadApp.ShowModalDialog(CadApp.MainWindow.Handle, dlg, false);
 
                if (res== System.Windows.Forms.DialogResult.Cancel)
                {
                    //restore the originals
                    UpdateTextEntity(OriginalText);
                    UpdateTextEntity(OriginalWidthFactor);
                }
            }
        }
 
        private void UpdateTextEntity(string textString)
        {
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var txt = (DBText)tran.GetObject(SelectedId, OpenMode.ForWrite);
                txt.TextString = textString;
                tran.Commit();
            }
            _dwg.Editor.UpdateScreen();
        }
 
        private void UpdateTextEntity(double widthFactor)
        {
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                var txt = (DBText)tran.GetObject(SelectedId, OpenMode.ForWrite);
                txt.WidthFactor = widthFactor;
                tran.Commit();
            }
            _dwg.Editor.UpdateScreen();
        }
 
        #endregion
    }
}

I need UI as a modal dialog box where user can re-typing text string, or change WidthFactor. It looks like this:


Here the dialog box' code behind:

using System;
using System.Windows.Forms;
 
using Autodesk.AutoCAD.DatabaseServices;
 
namespace RealTimeTextEdit
{
    public partial class dlgTextBox : Form
    {
        private RealTimeTextEditor _tool = null;
        private ObjectId _textId = ObjectId.Null;
        private bool _setText = false;
 
        internal dlgTextBox()
        {
            InitializeComponent();
        }
 
        internal dlgTextBox(RealTimeTextEditor tool):this()
        {
            _tool = tool;
        }
 
        internal event TextStringChangedEventHandler TextStringChanged;
        internal event TextWidthFactorChangedEventHandler TextWidthFactorChanged;
 
        internal bool Undo { private setget; }
 
        #region private methods
 
        private void ShowSelected()
        {
            if (!_tool.SelectedId.IsNull)
            {
                _setText = true;
                _textId = _tool.SelectedId;
                txtOld.Text = _tool.OriginalText;
                txtNew.Text = _tool.OriginalText;
                txtNew.Enabled = true;
                txtFactor.Text = _tool.OriginalWidthFactor.ToString("#0.00");
                udFactor.Value = Convert.ToDecimal(_tool.OriginalWidthFactor);
                btnUndo.Enabled = false;
                _setText = false;
            }
        }
 
        #endregion
 
        private void txtNew_TextChanged(object sender, EventArgs e)
        {
            if (_setText) return;
 
            btnUndo.Enabled = true;
            TextStringChanged?.Invoke(
                this, 
                new TextStringChangedEventArgs(txtNew.Text));
        }
 
        private void udFactor_ValueChanged(object sender, EventArgs e)
        {
            if (_setText) return;
 
            btnUndo.Enabled = true;
            TextWidthFactorChanged?.Invoke(
                this, 
                new TextWidthFactorChangedEventArgs(Convert.ToDouble(udFactor.Value)));
        }
 
        private void btnUndo_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
        }
 
        private void btnClose_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
        }
 
        private void dlgTextBox_Load(object sender, EventArgs e)
        {
            ShowSelected();
        }
    }
 
    internal class TextStringChangedEventArgs : EventArgs
    {
        internal string TextString { private setget; }
        internal TextStringChangedEventArgs(string textString)
        {
            TextString = textString;
        }
    }
 
    internal delegate void TextStringChangedEventHandler(
        object sender, TextStringChangedEventArgs e);
 
    internal class TextWidthFactorChangedEventArgs : EventArgs
    {
        internal double WidthFactor { private setget; }
        internal TextWidthFactorChangedEventArgs(double widthFactor)
        {
            WidthFactor = widthFactor;
        }
    }
 
    internal delegate void TextWidthFactorChangedEventHandler(
        object sender, TextWidthFactorChangedEventArgs e);
}

As the code shows, the real-time change to the target DBText/AttributeReference is triggered by the UI firing event TextStringChanged and TextWidthFactorChanged. The event handlers (highlighted in red) make the actual change.

See this video that shows the code in action.

It is worth pointing out: in the CommandClass RealTimeTextEditor I use non-static method as the CommandMethod, which means for each Document this command is used, a class instance is created by AutoCAD, so that the user's previous choice of editing target (Text, or Attribute), which is asked at beginning of the command execution, is the default choice (default keyword in the GetKeyword() call). The video clip shows this effect in its last portion.

Note:
Most of my posts come with video clips that show how the code run in AutoCAD. Until this post, I have always used Jing from TechSmith with free video host at screencast.com (thanks for the free stuff!). However, TechSmith recently announced their policy change that from now on, they would only keep video clips for one year for free Jing version user. So, starting from this post, I stopped using free Jing to generate screen capture video. Instead, I use OBS Studio to record screen as MP4 files and store and share them from my Google drive. I'll try to collect all the video clips used in my posts into my Google drive and update the links in the old posts as soon as possible before they are removed from screencast.com (hopefully I can find enough time before the deadline in September :-().

No comments:

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.