December 2014

Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      










« Adding a textbox to AutoCAD’s ribbon using .NET | Main | Back from a long weekend »

September 16, 2011

Adding an expanding textbox to AutoCAD’s ribbon using .NET

In the last post, we saw a great little sample for adding a textbox to AutoCAD’s ribbon which notifies your application of the “commands” entered into it (however you choose to interpret them in your code).

In this post, we’ll take that further and have that textbox expand vertically as text gets entered, wrapping the contents across multiple lines (only breaking the text at the ends of words, too).

Here’s the updated C# code, with modified lines in red. You can get the original file here.

    1 using Autodesk.AutoCAD.ApplicationServices;

    2 using Autodesk.AutoCAD.EditorInput;

    3 using Autodesk.AutoCAD.Runtime;

    4 using Autodesk.Windows;

    5 using System.Windows.Media;

    6 using System.Windows.Controls;

    7 using System.Windows.Input;

    8 using System.Windows;

    9 using System;

   10 

   11 namespace NotifyingRibbonTextbox

   12 {

   13   public class Commands

   14   {

   15     public bool _added = false;

   16 

   17     [CommandMethod("RTB")]

   18     public void RibbonTextBox()

   19     {

   20       if (!_added)

   21       {

   22         // Look for the standard "Plug-ins" tab

   23 

   24         RibbonControl rc = ComponentManager.Ribbon;

   25         RibbonTab rt = null;

   26 

   27         foreach (RibbonTab tab in rc.Tabs)

   28         {

   29           if (tab.AutomationName == "Plug-ins")

   30           {

   31             rt = tab;

   32             break;

   33           }

   34         }

   35 

   36         // If we didn't find it, create a custom tab

   37 

   38         if (rt == null)

   39         {

   40           rt = new RibbonTab();

   41           rt.Title = "Custom";

   42           rt.Id = "ID_CUSTOMRIBBONTAB";

   43           rc.Tabs.Add(rt);

   44         }

   45 

   46         // Create our custom panel, add it to the ribbon tab

   47 

   48         RibbonPanelSource rps = new RibbonPanelSource();

   49         rps.Title = "Notifying Textbox";

   50         RibbonPanel rp = new RibbonPanel();

   51         rp.Source = rps;

   52         rt.Panels.Add(rp);

   53 

   54         // Create our custom textbox, add it to the panel

   55 

   56         NotifyingTextBox tb =

   57           new NotifyingTextBox(150, 15, 17, 5);

   58         tb.IsEmptyTextValid = false;

   59         tb.AcceptTextOnLostFocus = true;

   60         tb.InvokesCommand = true;

   61         tb.CommandHandler = new TextboxCommandHandler();

   62         rps.Items.Add(tb);

   63 

   64         // Set our tab to be active

   65 

   66         rt.IsActive = true;

   67 

   68         // We only want to add it once, so set a flag

   69 

   70         _added = true;

   71       }

   72     }

   73 

   74     public static void Print(string s)

   75     {

   76       // A simple helper to write to the command-line

   77 

   78       Document doc =

   79         Autodesk.AutoCAD.ApplicationServices.

   80         Application.DocumentManager.MdiActiveDocument;

   81       doc.Editor.WriteMessage(s);

   82     }

   83   }

   84 

   85   public class TextboxCommandHandler : ICommand

   86   {

   87 #pragma warning disable 67

   88     public event EventHandler CanExecuteChanged;

   89 #pragma warning restore 67

   90 

   91     public bool CanExecute(object parameter)

   92     {

   93       // Yes, we can execute

   94 

   95       return true;

   96     }

   97 

   98     public void Execute(object parameter)

   99     {

  100       // Dump the textbox contents to the command-line

  101 

  102       NotifyingTextBox tb = parameter as NotifyingTextBox;

  103       if (tb != null)

  104       {

  105         Commands.Print(

  106           "\nRibbon Textbox: " +

  107           tb.GetTextWithoutNewlines() + "\n"

  108         );

  109         tb.ClearText();

  110       }

  111     }

  112   }

  113 

  114   public class NotifyingTextBox : RibbonTextBox

  115   {

  116     double _baseHeight;

  117     double _baseWidth;

  118     double _heightPadding;

  119     double _widthPadding;

  120     bool _textChanging = false;

  121 

  122     public NotifyingTextBox(

  123       double width, double height,

  124       double widthPadding,

  125       double heightPadding

  126     )

  127     {

  128       // Set some member variables, some of which

  129       // we also use to set the TextBox dimensions

  130 

  131       _baseWidth = width;

  132       _baseHeight = height;

  133       _widthPadding = widthPadding;

  134       _heightPadding = heightPadding;

  135 

  136       Width = width;

  137       Height = height;

  138       MinWidth = width;

  139 

  140       // Register our focus-related event handlers

  141 

  142       EventManager.RegisterClassHandler(

  143         typeof(TextBox), TextBox.GotKeyboardFocusEvent,

  144         new RoutedEventHandler(OnGotFocus)

  145       );

  146 

  147       EventManager.RegisterClassHandler(

  148         typeof(TextBox), TextBox.LostKeyboardFocusEvent,

  149         new RoutedEventHandler(OnLostFocus)

  150       );

  151 

  152       // And out additional TextChanged event handler

  153 

  154       EventManager.RegisterClassHandler(

  155         typeof(TextBox), TextBox.TextChangedEvent,

  156         new RoutedEventHandler(OnTextChanged)

  157       );

  158     }

  159 

  160     public string GetTextWithoutNewlines()

  161     {

  162       // Return the contained text without newline characters

  163 

  164       return TextValue.ReplaceNewlinesWithSpaces();

  165     }

  166 

  167     public void ClearText()

  168     {

  169       TextValue = "";

  170     }

  171 

  172     private void OnTextChanged(

  173       object sender, RoutedEventArgs e

  174     )

  175     {

  176       if (!_textChanging && e != null && e.Source != null)

  177       {

  178         TextBox tb = e.Source as TextBox;

  179 

  180         if (tb != null)

  181         {

  182           // We need the typeface to calculate the text width

  183 

  184           var faces = tb.FontFamily.GetTypefaces();

  185           Typeface face = null;

  186           foreach (Typeface tf in faces)

  187           {

  188             if (tf != null)

  189             {

  190               face = tf;

  191               break;

  192             }

  193           }

  194 

  195           // Get the last line of text, to see how long it is

  196 

  197           string text = tb.Text.GetLastLine();

  198 

  199           // Calculate the width of this last line of text

  200 

  201           FormattedText ft =

  202             new FormattedText(

  203               text,

  204               System.Globalization.CultureInfo.CurrentCulture,

  205               FlowDirection.LeftToRight,

  206               face,

  207               tb.FontSize * (96.0 / 72.0),

  208               Brushes.Black

  209             );

  210 

  211           // If the width of the last line of text

  212           // is over our boundary (the width of the box

  213           // minus some padding), then start the next

  214           // line

  215 

  216           if (ft.Width - _widthPadding > _baseWidth)

  217           {

  218             // Set our flag to stop re-entry of the event

  219             // handler, then replace the last space in the

  220             // TextBox contents with a newline

  221 

  222             _textChanging = true;

  223             tb.Text =

  224               tb.Text.InsertNewlineAtLastSpace();

  225             _textChanging = false;

  226 

  227             // Set the cursor to be at the end of our text

  228             // so that typing continues properly

  229 

  230             tb.SelectionStart = tb.Text.Length;

  231           }

  232 

  233           // Find the number of lines of text

  234 

  235           int lines = tb.Text.GetLineCount();

  236 

  237           // Change the height based on the number of lines

  238 

  239           tb.Height = _heightPadding + (lines * _baseHeight);

  240           tb.MinLines = lines;

  241         }

  242       }

  243     }

  244 

  245     // Both events call the same helper, with a custom message

  246 

  247     private void OnGotFocus(object sender, RoutedEventArgs e)

  248     {

  249       OnFocusChange(sender, e, "\nTextbox got focus :)\n");

  250     }

  251 

  252     private void OnLostFocus(object sender, RoutedEventArgs e)

  253     {

  254       OnFocusChange(sender, e, "\nTextbox lost focus :(\n");

  255     }

  256 

  257     // Our helper function to print  a message only when

  258     // our custom textbox exists

  259 

  260     private void OnFocusChange(

  261       object sender, RoutedEventArgs e, string msg

  262     )

  263     {

  264       if (e != null && e.Source != null)

  265       {

  266         TextBox tb = e.Source as TextBox;

  267 

  268         if (tb != null)

  269         {

  270           NotifyingTextBox mtb =

  271             tb.DataContext as NotifyingTextBox;

  272 

  273           if (mtb != null)

  274           {

  275             Commands.Print(msg);

  276           }

  277         }

  278       }

  279     }

  280   }

  281 

  282   // String-related extension methods

  283 

  284   public static class StringExtensions

  285   {

  286     const string newline = "\r\n";

  287 

  288     public static string InsertNewlineAtLastSpace(

  289       this string s

  290     )

  291     {

  292       // If the string contains a space, replace it

  293       // with a newline character, otherwise we simply

  294       // append the newline to the string

  295 

  296       string ret;

  297       if (s.Contains(" "))

  298       {

  299         int index = s.LastIndexOf(" ");

  300         ret =

  301           s.Substring(0, index) + newline +

  302           s.Substring(index + 1);

  303       }

  304       else

  305       {

  306         ret = s + newline;

  307       }

  308       return ret;

  309     }

  310 

  311     public static string ReplaceNewlinesWithSpaces(

  312       this string s

  313     )

  314     {

  315       // Replace all the newlines with spaces

  316 

  317       if (String.IsNullOrEmpty(s))

  318         return s;

  319       return s.Replace(newline, " ");

  320     }

  321 

  322     public static string GetLastLine(this string s)

  323     {

  324       // Return the last line of the text (or

  325       // the whole thing if there's no newline in it)

  326 

  327       string ret;

  328       if (s.Contains(newline))

  329       {

  330         ret =

  331           s.Substring(

  332             s.LastIndexOf(newline) + newline.Length

  333           );

  334       }

  335       else

  336       {

  337         ret = s;

  338       }

  339       return ret;

  340     }

  341 

  342     public static int GetLineCount(this string s)

  343     {

  344       // Count the number of lines by checking the

  345       // overall length of the string against the

  346       // string without newline sequences in it

  347 

  348       return

  349         (

  350           (s.Length -

  351             s.Replace(newline, "").Length)

  352           / newline.Length

  353         ) + 1;

  354     }

  355   }

  356 }

In terms of changes to the code, the main things to note are some additional parameters on the constructor of the NotifyingTextBox class (which control the size and behaviour of the textbox), where we also attach another event handler to be called when the contents of the textbox are modified.

In this event handler, we calculate the width of the last line of text (basically the one being typed), to see whether we need to break the line or not. We use the FormattedText class to do this, which uses the font and its size from the textbox (with some modification to convert the size, as per this information). If the line is wider than the width of our textbox – taking into account an additional buffer amount, just to make it work better – we insert a newline at the preceding space, which keeps the entered words intact.

Something to note: we check a flag in the event handler to stop re-entering when we modify the text by inserting the newline. If we didn’t do this we’d soon get ourselves in trouble (trust me on this ;-).

Otherwise, the code itself should be reasonably straightforward. It uses some extension methods on the string class to make life a little easier, but that's about it.

Putting this updated code through its paces…

Now when we run the RTB command and start to enter text into our textbox, we see it expands as we get to the end of a line:

Our textbox now expands

And it keeps on going:

And expands

When we’ve finished, it echoes the contents – without the additional line-breaks – to the command-line:

Textbox got focus :)

Ribbon Textbox: Let's now enter a longer string of text, to see what

happens when we write and write and write and write and write - we

actually see the ribbon starting to stretch, which is pretty cool.

Textbox lost focus :(

I didn’t manage to get the ribbon to stretch horizontally: the width seems based on the size of the controls you place on it at startup, so while we can make the textbox stretch horizontally, getting the ribbon panel to accommodate the larger size proved harder (i.e. I couldn’t work out how to do it). Please post a comment if you’ve managed to do this, yourself.

blog comments powered by Disqus

Feed/Share

10 Random Posts