Mark Miller
  • CodeRush for Roslyn, v1.0.6

    CodeRush for Roslyn v1.0.6 is ready for download. Here’s what’s new in this version:

    • The Decompiler is now included in CodeRush for Roslyn v1.0.6. Assembly structure, from namespace all the way down to class members, appears in the tree view on the left, while source code for the selected node appears in a pane on the right.

      DecompilerWindow.png

    • The Unit Test Runner shows test execution progress and summarizes test run results on Visual Studio’s status bar.
    • New test runner actions (which can be bound to shortcuts for fast invocation):

    Command Behavior Default Shortcut
    UnitTestsRunCurrentClass Executes all tests inside the active type (at the caret) Ctrl+T, C
    UnitTestsRunFile Executes all tests contained in the active file Ctrl+T, F
    ShowTestRunner Shows the Unit Test Runner window Ctrl+T, T

      • New Code Cleanup rules:
        • Collapse Accessors
        • Expand Accessors
        • Remove redundant ‘base’ qualifier
        • Remove redundant ‘this’ qualifier
        • Remove redundant type cast
        • Use explicit ‘this’ qualifier for field access
        • Use explicit/implicit visibility modifiers

      • Member Icons – select member scope.
        MemberIconMenu.png

      • Cycle Scope (member/type visibility) Up/Down with Alt+Up/Down
      • New Selection Embeddings wrap the selected expression inside parens when you press one of the paren keys. Enable these bindings if you want this feature:

        image 

        Also, if you work with a non-U.S. keyboard, check the bindings and redefine them if needed to correspond with the keys you press for the left and right parentheses, and the “!” character.

      • New for Refactorings and Code Providers:
        • Declare Constant - declares a constant for the primitive value at the caret.
        • Introduce Setter Guard Clause - introduces a value-changed check at the start of a property setter, exiting early if the assigned value matches what is already in the backing store.
        • Optimize Namespace References gets a new options page, letting you specify sorting options. You can also use this page to specify which references should be never removed even when they aren’t used.

          image
        • Rename File to Match Type - renames the file (and updates the project), so the file name matches the type name.
        • Remove Unused Member - removes empty and unused event handlers.
        • Now available for Visual Basic:
          • Improved support for code providers declaring classes and members.
          • Add Parameter
          • Declare Method
        • New refactorings for C#:
          • Collapse Property - collapses a property with backing store to a single line if its accessors are empty or contain a single one-line statement.
          • Collapse Accessor - Collapses an accessor to a single line if it is empty or contains a single one-line statement.
          • Collapse Method - collapses a method to a single line if its body is empty or contains a single one-line statement.
          • Expand Property - expands a property, placing its getter and setter on separate lines.
          • Expand Accessor - expands an accessor placing its content on a separate line.
          • Expand Method - expands a single-line method placing its content on a separate line.

       

      • Navigation Providers, accessible by pressing Ctrl+Alt+N:
        • Assignments
        • Base Types
        • Declaration
        • Derived Types
        • Implementations
        • Instantiations
        • Members
        • Overloads
        • Overridden Member
        • Overrides
        • Parent Type

      NavigationProviders.png

      The CodeRush for Roslyn preview is free, and updated every 45 days.

      As usual, please download, give it a try – and let us know how we can make it better (support@devexpress.com).

    • CodeRush for Roslyn, v1.0.5

      The CodeRush team, continuing its 45-day or less release pace, is ready to bring you the latest update to CodeRush for Roslyn, version 1.0.5.

      Our previous release, CodeRush for Roslyn v1.0.4, included new refactorings, code providers, text commands, and test runner and code coverage support. You can download the latest version of CodeRush for Roslyn from the Visual Studio Gallery.

      Zero Bug Policy

      The team has a zero-bug policy, which essentially means we resolve all reported issues before we write new features. And so in this release, v1.0.5, we’ve prioritized quality and performance over new/ported functionality, resolving over 200 issues, including:

      • Templates:
        • Expansions in split views
        • Corrected context to prevent unintended expansions inside XML doc comments and interpolated strings
        • Corrected a number of template expansion issues in Visual Basic
      • Exceptions when editing code
      • Performance issues:
        • Toggle Comment
        • Test Runner in large files with a huge number of test cases
      • An edge case out-of-memory exception
      • Edge case deadlocks on solution open/close and Test Runner build
      • Edge case crashes (when rename is invoked in navigation link, or when double-clicking tests in the Test Runner)

      Other feature areas receiving improvements in this release:

      • Code Cleanup
      • Code Coverage
      • Code Providers
      • IntelliRush
      • Linked Identifiers
      • Refactorings
      • References tool window
      • Shortcuts
      • Tab to Next Reference
      • Text Fields
      • Test Runner

      We also added hundreds of test cases to help ensure these issues never appear again.

      New in v1.0.5:

      We added the following new features in v1.0.5:

      • CodeRush now updates test run progress on the Windows taskbar.

        TestRunnerProgressOnTaskBar 
      • The Test Runner filter now shows the full path to each test, so you can easily find the test you are looking for even if you have tests with identical names in different test fixtures.
      • We added the "Add Else Statement" and “Declare Interface” code providers.
      • Smart Constructor, Declare Class, Declare Property, and Declare Property (with field) are now available for Visual Basic developers.

      Give it a Try

      Give the 1.0.5 version a try and let us know what you think. You can download CodeRush for Roslyn from the Visual Studio Gallery.

    • CodeRush for Roslyn v1.0.4

      CodeRush for Roslyn (CRR) v1.0.4 is now available. This free preview version expires on 4 September 2015.

      Here’s a list of what’s new in version 1.0.4.

      New TextCommands:

      • Smart Return, used in the r and lr templates.
      • Smart Constructor (C#), used in the cc template.
      • ForEach, used in the sw and asm templates.

      New Editor Features:

      New Refactorings and CodeProviders:

      VB Support added for:

      Unit Test Runner enhancements:

      • Performance improvements.
      • Ability to exclude selected categories when running all tests.

      Code Coverage enhancements:

      • Navigate from code to the corresponding Code Coverage tree node.

        ShowCoverage 
      • Active method highlighting inside the Code Coverage code view window.

      Download it, try it out, let us know what you think.

    • IntelliRush Hierarchical Filtering

      In CodeRush Classic 15.1 and in CodeRush for Roslyn we added the ability to filter Intellisense members by the class where they are declared.

      Here’s how it works. To show this in action, I created a new Windows Universal App, and opened the MainPage.xaml.cs file. Inside the constructor, I typed “this.”

      Here’s what I see:

      IntelliRush

      That’s about 165 entries. That’s a lot to wade through. I can reduce the list if I have an idea of where to look in the hierarchy.

      With IntelliRush’s new Hierarchical Filtering, this is easy. I tap the Ctrl key…

      IntelliRushCtrlKeyPressed

      On the right I see “Hierarchy”.

      So I press the letter “h” and I see the class hierarchy listed on the right, from MainPage (the type of the “this” reference) up to Object

      IntelliRushHierarchicalMenuUp

      Now, if I know the class I want to see members from, I can simply press the number associated with that class. For example, if I only want to see members from the FrameworkElement class shown in this list, I simply press the number 4 from the hint, and my list of 165 members drops to 34:

      IntelliRushFrameworkElementEntriesOnly

      If I want to slice the class hierarchy to include a range of classes, for example UIElement and up, I can do the following:

      1. Tap Ctrl
      2. Press h

        The Hierarchy hint displays:

        IntelliRushHierarchicalMenu

        UIElement is number 5. So if I want to see members declared in UIElement and all its ancestors, all I need to do is…
      3. Press Shift+5

      And with those three keystrokes IntelliRush shows only the entries declared in UIElement and up:

      IntelliRushShowingObjectToUIElement

      Shift + the class number shows members declared in the specified class and above.
      Ctrl + the class number shows members declared in the specified class and below.

      On most keyboards the Shift key is above the Ctrl key, so you may find their position (Shift above, Ctrl below) helpful in remembering which modifier key to hit.

      The active class is always numbered zero, which allows for some useful shortcuts to keep in mind:

      Member Scope To filter, tap Ctrl, press H, and then:
      Active class only 0
      Parent class only 1
      Active & parent classes only Ctrl+1
      Parent class and above Shift+1
      All classes (resets an active filter) Shift+0

      Combining Filters

      You can combine a hierarchical filter with a member kind filter. Only want to see events declared in the active class or its ancestor? Easy. First, apply a hierarchical filter to isolate members to only the ancestry you’re interested in. Next, apply the member kind filter you want (e.g., tap Ctrl, then press E, to only see events).

      Try it Out

      IntelliRush’s Hierarchical Filtering is available in both CodeRush Classic 15.1 and CodeRush for Roslyn 1.0.

    • CodeRush for Roslyn (preview)

      So the entire team has been working hard on CodeRush for Roslyn. This endeavor is huge: Hundreds, if not thousands of language-based features, replacing our core engine with Roslyn’s core engine, and porting tens of thousands of test cases.

      Based on what we’ve seen so far, the end results, extremely efficient use of memory, even faster performance, and more, appear to justify the effort.

      The Plan

      The DevExpress 15.1 release includes two CodeRush products:

      1. CodeRush Classic 15.1 (previously known as simply CodeRush). This is the same CodeRush we’ve shipped for years. It includes the full feature set, however 15.1 will not include support for C# 6 and VB 14 language features.
      2. CodeRush for Roslyn (CRR) 1.0 preview. CRR will not include the full CodeRush 14.2 feature set (more details below), however it will include full support for C# 6 and VB 14 language features (and beyond).

      The Future of CodeRush Classic

      At some point in the future, when we have ported the entire CodeRush Classic feature set to CRR, we intend to deprecate CodeRush Classic. We will continue to support and fix issues in CodeRush Classic for some time beyond this deprecation point, however CodeRush Classic is unlikely to ever get support for new language features (e.g., C# 6, VB 14, and beyond).

      Which CodeRush Should I Use?

      If you’re working in Visual Studio 2015 with the new language features in C# or VB, you should install CodeRush for Roslyn. If you rely on CodeRush Classic features that haven’t been ported yet, you’ll need CodeRush Classic. If you need both, you can install and use both (more on this in later posts).

      Benefits

      There are three significant benefits you can expect from CodeRush for Roslyn:

      Massive Reduction in Memory Consumption

      Refactoring tools need to understand the code. And to refactor and find references quickly, you need to parse the solution source. And that means memory. The bigger the solution, the more memory you need. In CodeRush Classic and in competing tools which have decided not to support Roslyn, the memory required is essentially doubled as the Visual Studio host is also parsing and storing similar results. Owners of huge solutions were hit hard when using tools like CodeRush Classic. With CodeRush for Roslyn, this doubling-up memory waste is a now thing of the past.

      To see this savings in action, we created two benchmarks using the following hardware and software:

      Machine: Intel® Core™ i7-363QM CPU, 2.40 GHz, 8GB RAM, SSD HD 
      OS: Windows 8.1 Enterprise 64-bit
      Software:

      • Visual Studio v14.0.22823.1 D14REL
      • CodeRush for Roslyn v0.9
      • Competing Product v9.1

      Solutions Tested:

      Benchmarks:

      1. Memory.

        Prep: Open solution. Build. Close all documents. Only Solution Explorer and Properties windows are active. Close solution. Close Visual Studio.

        Memory Test: Start Visual Studio, open solution, wait until devenv.exe process CPU usage falls to 0. Calculate managed memory using VSIX plug-in.

      2. Performance.

        Prep: Open Visual Studio with Start Page opened. Wait until all extensions are successfully loaded.

        Performance Test: Click the solution link the solution in the Recent tab and start the timer. Stop the timer when the solution loading progress bar is complete. For VS and CodeRush the progress bar appears in the Solution Explorer. Competing products may place a progress bar in the lower-left corner of the VS status bar.

      At DevExpress we have a policy of not mentioning competing products on our site, so I can’t reveal the name of the product we compared CodeRush to. However I can tell you the version number of the competing product we tested was 9.1, and I can tell you the competing product is one that has previously announced they would not exploit the Roslyn engine (which means you would expect their memory usage to be noticeably higher than Visual Studio’s).

      Results of the memory tests:

      MemoryUse

      For the small solution, DotNetOpenAuth, CodeRush uses only 6MB.

      For the medium-sized solution, Orchard, CodeRush uses 13MB.

      And for the large solution, opening the source code to Roslyn itself, CodeRush uses 55MB.

      Faster Performance

      Not only was CodeRush Classic storing essentially the same results as Visual Studio was, it was also parsing the same code a second time (just like some competing tools still do). That waste stops with CodeRush for Roslyn. Every feature works noticeably faster and feels snappier, while CRR adds only 0-2 seconds to solution-open times:

       SolutionOpenTimes

      Better Language Support

      Expect CodeRush for Roslyn to immediately understand and support new C# and VB language features as they are released by the Visual Studio team.

      Support for Languages Beyond C# and VB

      CodeRush for Roslyn will include parsers and code generators from the CodeRush Classic engine for any languages that Roslyn doesn’t support yet, including XAML, CSS, HTML, and XML. As Roslyn adds support for these languages, We’ll update CRR accordingly to exploit the new support, which should result in additional memory reduction and faster performance.

      What’s really exciting, is that as the Visual Studio team and third parties move new languages under the Roslyn engine, you can expect CodeRush for Roslyn to understand those as well. There may be some changes required on our side to support the new languages, however the effort is a small fraction of what it was before Roslyn.

      The Preview is Free

      Today we’re releasing an early preview of CodeRush for Roslyn on the Visual Studio Gallery. We intend to release updates every four weeks as we approach the final release. The install is VSIX-based, which means updates are quick and easy and can happen from inside Visual Studio.

      What’s New

      In addition to a port of CodeRush classic features (see below for details), CodeRush for Roslyn also includes two new features.

      IntelliRush Hierarchical Filtering

      In this release IntelliRush gets a great new feature, Hierarchical Filtering. This lets you slice up the Intellisense hierarchy to only see entries from specified classes in the ancestry.

      IntelliRushHierarchicalMenu

       

      Code Coverage

      The Code Coverage window shows which lines of code are covered by test cases and which are not.

       image

      Features Ported from CodeRush Classic

      Features included in the CodeRush for Roslyn (preview):

       image

       image

      We Need Your Feedback

      Let us know what you love, what you’re missing, and what you’d like changed. When CodeRush for Roslyn is published (awaiting final approval from the powers that be), it will be available here.

    • What’s New in CodeRush 14.2

      IntelliRush

      IntelliRush is the big feature in this release. IntelliRush enhances Visual Studio’s Intellisense, most importantly adding the ability to easily filter the list.

      You can filter to see extension methods only:

      FilteringByExtensionMethods

      You can filter to see regular methods only:

      MethodsOnly

      You can filter to see properties only:

      FilterPropertiesOnly

      You can filter to see only enums:

      FilterOnlyEnums

      You can filter to see only namespaces:

      O'nlyNamespaces

      You can filter to see only interfaces:

      InterfacesOnly

      To filter, just tap the Ctrl key. The filter hint will appear:

      FilterHint

      Then simply press the letter of the filter you want to apply. For more on IntelliRush, see this post.

      Debug Visualizer

      We continue to invest in and polish the Debug Visualizer. New in 14.2:

      Dead Path De-emphasis has been moved out of beta and is now a first class feature. Code paths that will not be executed, determined as you step through the code, are rendered in a reduced contrast.

      For example, in the code below, the instruction pointer is on the switch statement, but it has not been evaluated yet. However DV gives you a peek into the future and shows you exactly which case statement execution will flow to:

      DeadPathDeemphasis2

      Syntax highlighting is still visible in the dead code path. Dead code paths are now detected in more scenarios, including dead if or else branches, dead case statements and dead catch statements.

      Exception Filters in Visual Basic are now supported, with the filter value and associated Boolean icon clearly visible.

      Exception Variable Preview in all catch statements, even when the exception variable is not declared.

      Smoother Animation, keeps important information right where you are already looking.

      Behind the scenes, we updated the DV engine. It is more performant, and we improved JavaScript analysis.

      Spell Checking Member Names

      You can optionally check member names. To turn the option on, go into the Editor\Spell Checker options page, and check the “Name of members” checkbox:

      SpellCheckerMemberNames

      Once enabled, member names are all checked for correct spelling. Spelling issues are highlighted:

      InsideTeztMethod

      To correct or ignore the spelling anomaly, place the caret inside the misspelled portion, press the CodeRush key (e.g., Ctrl+` – Control plus the back tick key is the default.

      SpellChecker

      Correct the spelling mistake, and the member name is instantly updated everywhere.

      InsideTestMethod

      You can also add commonly used abbreviations to the dictionary. For example, in the code above, “ctx” is highlighted as not found. That’s an abbreviation I use for ContextProviders I drop on the design surface. So I can easily add that to the dictionary so I’ll never see that again.

      AddToDictionary

      Other Improvements

      • We’ve also improved the Decompiler, Jump to Declaration (with Partial Classes), and we’ve added automatic updates for CodeRush if you download and install from the Visual Studio Gallery. Here’s your complete list of What’s New in CodeRush 14.2.

      If you’d like to see all this in action, watch my What’s New in CodeRush 14.2 webinar scheduled for 5 December 2014 at 10:00am.

    • Pressing F5 in Test Methods to Test

      Last night, as I was about to stop work to spend some time with the family, I noticed this tweet:

      F5Run

      And I thought: You can do this in CodeRush. Easily. CodeRush has a remarkably flexible shortcut-binding system that can associate a sophisticated context (that must be satisfied) with any key binding. So it’s relatively simple to bind F5 to a command to run the test when the caret is inside a test method. You just need a context that tells the key-binding engine whether you’re in a test method or not.

      CodeRush ships about 200 different contexts, that can do anything from tell you whether you are inside a property’s getter to whether the active project references a particular assembly. Contexts are used in shortcut bindings as well as other editor features.

      After seeing the tweet, I checked to see if we already shipped a context that was satisfied when the caret was inside a test method. We did not.

      This morning I noticed more discussion about this feature, including a suggestion to use another shortcut (which increases user burden and cognitive load – lots of good reasons NOT to do this), followed by this tweet from Caleb Jenkins:

      AllCodeRushLike

      Time to get to work.

      Here’s how I built the feature, in about three minutes:

      1. CodeRush | New Plug-in.
      2. Dropped a ContextProvider control onto the design surface.
      3. Named it “Editor\Code\InTestMethod”.
      4. Double-clicked the ContextSatisfied event. Added this code:

        private void ctxInTestMethod_ContextSatisfied(ContextSatisfiedEventArgs ea)
        {   Method activeMethod = CodeRush.Source.ActiveMethod;   if (activeMethod != null && activeMethod.Attributes != null)     foreach (DevExpress.CodeRush.StructuralParser.Attribute attribute in activeMethod.Attributes)       if (attribute.Name != null && attribute.Name.StartsWith("Test"))       {         ea.Satisfied = true;         return;       }
        }
      5. Pressed F5 to run (to test my plug-in).
      6. In the new instance of VS, added the following CodeRush shortcut binding:

        ShortcutBinding

      And that’s it.

      Now I can press F5 inside a test method and only that method test is run. If I press F5 outside of a test method, the startup project runs.

      F5ToRunTestCases

       
    • Here’s your Game Changer for 2014: IntelliRush in CodeRush 14.2

      So you may have noticed, I only use the term “Game Changer” for features that dramatically change and improve the way you work as a developer. The last time I used the term was two years ago, when we introduced the Debug Visualizer for CodeRush.

      The CodeRush team has been working on a revisualization of Intellisense designed to enhance existing functionality and add new abilities. The end result we’re calling IntelliRush, and it’s faster and more capable of exploring and entering code than anything you’ve seen before.

      No Changes to the Way You Work

      Well, we’re going to make you faster, but if you choose to, you can use IntelliRush exactly the same way and pressing exactly the same keys you’re already familiar with. Also, you can enable or disable IntelliRush at any time using the IntelliRush button on the DX toolbar or on the Editor\IntelliRush options page. However, if you want more…

      See More

      Visual Studio’s built-in Intellisense restricts you to a small window that shows only nine entries (sometimes that small window is a view into hundreds of symbols to scroll through). Small views into big data can be frustrating and take a long time to navigate. IntelliRush helps you find what you’re looking for faster by letting you see more entries without scrolling. And you are free to resize the window to suit your preferences and style of working:

      IntelliRushShowsMore

      Auto-sizing Width

      Even though IntelliRush shows more entries, it continually works to only take up as much width as needed to show visible entries. This lets you see more code and places symbol hints closer to the entries, so your eyes don’t have to travel so far to the sides to read the hints.

      In the comparison below, watch how IntelliRush automatically narrows its width depending on the contents in view:

      IntelliRushAutoWidth

      Filtering (by Symbol Kind)

      One of the features we are most excited about is filtering. Have you ever tried to use the built-in Intellisense to find an event, property, interface, delegate, or class, only to realize you were spending a lot of time looking at everything else you didn’t want?

      Now, with the smallest effort, you can see a list of in-scope entries that exactly match the kind of symbol you’re looking for.

      Here’s how it works:

      1. Inside Visual Studio bring up IntelliRush (like you normally would bring up Visual Studio’s built-in Intellisense).
      2. Tap (quickly press and release) the Ctrl key.
      3. Press the highlighted letter of the filter you wish to apply.

      For example, to see only Events, simply tap the Ctrl key and then press the letter “E” key. IntelliRush will show only events:

      FilteringOnlyEvents

      To see only Properties, simply tap the Ctrl key and then press the letter “P” key:”"

      FilteringOnlyProperties

      You can filter on:

      • Namespaces
      • Classes
      • Structs
      • Interfaces
      • Enums
      • Delegates
      • Methods
      • Properties
      • Events
      • Fields
      • Locals
      • Visual Studio Code Snippets

      Got it? Simple. Easy. Fast. And it helps you narrow down on what you want, obliterating the visual noise caused by everything else.

      Filtering by Symbol Text

      Now when you filter the list by typing text in the editor, you can see your filter as it applies to each entry.

      FilterVisualization1 FilterVisualization3

      Overload Exploration

      Many developers use Intellisense as an exploration tool, to learn about frameworks and classes declared in large solutions or in referenced assemblies. Here’s an example of what Visual Studio’s built-in Intellisense gives you when you’re on a method with overloads:

      IntellisenseOverloads

      IntelliRush provides this as well, and then takes it to the next level, giving you a syntax-highlighted submenu for the method overloads (available if you press the Right arrow key).

      SubmenuDetail

      Now you can easily compare the overloads side-by-side, and select the one you want. After selecting an overload, CodeRush can optionally insert TextFields for you into the code, so after specifying an argument, you can jump to the next argument by simply pressing Enter. It looks like this:

      IntelliRushTextFields

      More Enhancements Coming

      Unfortunately we didn’t have enough time to squeeze all the IntelliRush features we wanted into this 14.2 release. The good news is that significant usability enhancements (to improve the speed and ease of exploration, and to widen IntelliRush scope to include CodeRush templates) are expected in future releases.

      Try IntelliRush out and let us know what you think.

    • Creating the Ultimate Developer’s Keyboard–Part 5

      Here’s what we’ve done so far:

      Part 1 - the challenge, hardware, and the prototype.
      Part 2 - first features - paired delimiters.
      Part 3 – keyboard arrives, layout, keys labels, feature binding, plus new related features.
      Part 4 – smarter brace keys, recent edits navigation, section navigation, and the pizza key.

      Also, from a big picture perspective, we’ve taken a rough idea for making important features more accessible and we’ve taken a step in that direction, using off the shelf hardware. And while this post will likely be the last in this particular series, you can be assured that I’m thinking about taking this to the next level, with a full keyboard designed for developers everywhere (maybe a kickstarter campaign is in our future).

      Super Sibling Nav Returns

      One of my favorite features ever built in a CodeRush Feature Workshop was Super Sibling Nav. This navigation feature allows you to move up/down through neighboring methods or properties, maintaining relative cursor position as you move up and down. This is not something you need often, but when you need it (for example, examining or changing similar parts of adjacent methods), it can save a lot of time and keep your brain focused on what’s important. The problem with this feature was always the shortcut binding. What key can we bind it to that is easy to remember and easy to hit? After we built the feature we tried Ctrl+Alt+Page Up/Down. However that binding never really took off with me.

      For me the good news is that I’ve dedicated two keys for this feature:

      SuperSiblingNavKeys

      If you want to use this feature too, download and install CR_SuperSiblingNav.

      We’ll add the bindings for this later.

      Repositioning the Pizza Key

      In Part 4 we added the Pizza key. And while the key we replaced was a good one, I didn’t like its position on the keyboard. The Pizza key was likely a key that would be pressed once per day at most, and it was right next to a key that was used frequently. Also, the Pizza functionality is visually disruptive, ultimately replacing the code with pictures of yummy food. Not the thing you want to see when you’re deep in focus. So I moved it to the upper right, and moved the Run Last Test key to the upper left, and moved the the Fields key to the right where the Pizza key used to be. The new layout looks like this:

      NewPizzaKeyPosition2

      This repositioning required the following changes:

      • I needed to update the Layout options page to reflect the new keyboard layout.
      • I needed to update the Pizza, Field, and Last Test Run shortcuts so they were using the new key positions.

      All the Bindings

      Instead of talking you through setting up each of the bindings, instead I’ll document the functionality we have created (and we’re binding to) in the table below:

      Key Feature
      Pizza  It’s the Pizza key. Brings up the Nom Nom window that allows you to quickly choose from several built-in vendors of programmer’s fuel. You can also click the Find buttons to find a pizza vendor location near you. There’s also an option to go straight to the order page skipping the window altogether (if you check this option, hold down the Shift key the next time you hit the Pizza key to bring the window back).
      Transporter The Transport key. This key brings up the “Jump to” menu, which allows you select a target location for the jump, such as method overrides, interface implementors, ancestor classes, etc., depending upon where you are in the code.
      CodeRush The CodeRush key. This key brings up a Code/Refactor menu, that shows available refactorings, code declarations, and code modification wizards. The contents of this menu depend upon where you are in the code.
      ArrowPlusEnter The arrow keys and the Enter key. These keys let you navigate through the Code/Refactor and the “Jump to” menus quickly without having to move your hand away from the keyboard.

      The Enter key has an additional binding if the editor has focus (and no menus are active). It adds a new line beneath the current line regardless of the caret position on the current line (it’s the equivalent of pressing the End key followed by Enter).
      Fields Navigates through all the field declaration sections in the active class. This is useful to quickly get the caret to the fields declaration section. This feature drops a marker so you can quickly get back to where you started with Escape.
      AltKey + Fields Navigates through all the const declaration sections in the active class.
      Properties Navigates through all the property declaration sections in the active class.
      AltKey + Properties Navigates through all the event declaration sections in the active class.
      Methods Navigates through all the method declaration sections in the active class.
      AltKey + Methods Navigates through all the constructor declaration sections in the active class.
      PreviousReferenceNextReference Navigates through all the references (inside the solution) of the active type, member, declaration, or string at the caret.
      CamelCaseNavPreviousCamelCaseNavNext Camel Case Navigation – navigates to the next uppercase character inside a camel case identifier.
      SelectionIncrease Extends the selection by a logical block (for each time it is pressed).
      MinusKey Shrinks a previously-extended selection.
      SwapAnchorWithActive Swap the selection’s anchor and active positions (useful for extending either side of a selection).
      SelectToMarker Select from the caret position to the topmost marker in the file.
      SpacerNodeUp
      NodePreviousFirstChildNodeRight
      Structure Navigation keys.

      NodeUp takes you to the parent node.

      FirstChild takes you to the first child node.
      NodePrevious and NodeRight take you between adjacent sibling nodes.
      AltKey + FirstChild Takes you to the last child node.
      ShiftKey + NodePrevious Extends the selection to include the node preceding the start of the selection.
      ShiftKey + NodeRight Extends the selection to include the node immediately following the end of the selection.
      ClipboardHistory Brings up the Clipboard History.
      LeftBraceRightBrace
      OpenParenRightParen
      OpenBracketCloseBracket
      DoubleQuotesDoubleQuotes
      LeftAngleBracketRightAngle
      Paired Delimiter Keys. These keys insert the specified paired delimiters and position the caret in the desired location. For more details on this feature, see part 2 and part 3 of this series.

      If a selection exists, the selection is wrapped in the delimiters. Otherwise the delimiters are inserted at the caret. Caret position is determined by which of the two key pairs is pressed.

      Pressing the left (opening) key means the caret goes to the left if there is a selection. If there is not a selection pressing the left key means the caret will go between the delimiters (with a TextField between so you can get outside the delimiters by pressing Enter).

      Pressing the right (closing) key will position the caret to the right of the delimiters after insertion.

      Additionally, the brace keys LeftBrace & RightBrace can be used to add or remove redundant brace delimiters around child statements of a parenting block (such as if, for, while, etc.). Just place the caret at the start of the child statement, or place the caret after the opening redundant brace and then press one of these keys.
      DropMarker Drop a navigation marker.
      Escape

      Cancels CodeRush menus (if they are up) and also collects any markers that have been dropped.

      SwapMarkerWithCaret Swap the caret position with the last marker (and dropping a marker before jumping to the last position). This effectively allows you to work in two places at once, quickly switching back and forth between locations.
      SuperSiblingNavNextSuperSiblingNavPrevious Navigate up and down between sibling members, maintaining the relative cursor position or selection between adjacent members.
      NextViewEditPreviousViewEdits Navigate back and forward through previous edit points in the code.
      BrowseRecentFiles Browse recently-opened file history
      QuickNav Find Any Symbol
      CtrlKey + QuickNav Find Any Member
      AltKey + QuickNav Find Any Type – a filtered version of Find Any Symbol.
      GotoDefinition Go to Declaration (press Escape to jump back)
      Options CodeRush Options
      ShiftKey + Options Visual Studio Options
      CtrlKey + Options Project Properties (for the active project)
      RunLastTests Run Last Test

      You can see most of these shortcuts in action in STL Tech Talk episode 23.

      Source & Settings Files

      You can get all the shortcut bindings here. These should be copied to the CodeRush Settings folder.

      Source code to the x-keys engine plug-in (for CodeRush in Visual Studio) is here.

      Source code to the new features plug-in we built in this series is here.

      Note: Before compiling the source code to the two plug-ins, specify the build output folder to match your CodeRush plug-ins folder.

      You can open the settings and plug-in folders in Windows Explorer quickly by right-clicking the orange banner in the CodeRush About box and choosing the desired folder to open. After the DLLs have been built and the settings have been copied, restart Visual Studio.

      Wrapping It Up

      So this has been an interesting exploration. The custom keyboard is useful, and some of the new features are arguably essential. However, it’s not perfect. And I really want it to be perfect. Taking this to the next level, ideas to consider for my next attempt may include:

      1. A custom hardware solution with Cherry MX keys.
      2. Groups of keys dedicated to navigation, code generation, refactoring, and selection, placed to the right and left of the standard 88-key arrangement, with the ability to snap in securely on either side make a single solid keyboard, or float apart separately for more optimal positioning.
      3. Ergonomic and standard layouts.
      4. Possibly a group of keys dedicated to context-based operations (e.g., code entry, debugging, form layout & design) with embedded OLED screens that change content based on context.
      5. Other neat/fun ideas.

      Keyboard2

      Thanks for reading. Let me know what you think.

    • Creating the Ultimate Developer’s Keyboard–Part 4

      Here’s what we’ve done so far:

      1. In part 1, we introduced the challenge, discussed hardware options, and revealed the prototype.
      2. In part 2, we wrote our first features (before the keyboard even arrived!) to help us enter paired delimiters like braces, parens, and quotes.
      3. In part 3, we reviewed the keyboard, created our layout, labeled the keys, bound the features we wrote in part 2, and added new related features that added or removed braces automatically for child nodes of if statements.

      Making the Brace Keys Even Smarter

      I believe I might have some kind of obsessive compulsive behavior. Once I start thinking about something I want to fix, I obsess about it more and more until I finally have to fix it. And yesterday we built a feature that added or removed surrounding braces to orphan child statements. And while functionality was good, accessibility was overly complicated. We essentially bound a toggling feature to two different shortcuts. That’s not good. It increases our mental burden because we have to remember which of the two keys adds or removes braces. I want to remove this burden and make it so either of the two brace keys can be hit, and I want the software to intelligently invoke the appropriate refactoring (if available), and if neither refactoring is available, we can fall back to the default paired delimiter behavior we built in part 2.

      So let’s open our CR_KeyFeatures plug-in. Remember to delete the previously-built CR_KeyFeatures.dll before opening (see “Updating and Rebuilding in Future Sessions" in part 2 for steps on how to do this).

      We’re going to add a ContextProvider so we can improve our shortcut binding, and make it appear even smarter. Here’s how to do it:

      1. Open up the designer for the Plugin1.cs file.
      2. In the Toolbox, find the ContextProvider control (tip – type a part of the control name into the search box at the top).

        ContextProvider
      3. Drop the ContextProvider on the Plugin1 design surface.
      4. Fill out the following properties for the new ContextProvider:

        Property Value
        (Name) ctxBraceRefactoringAvailable
        Description Satisfied if one of the two brace refactorings are available (Remove Redundant Block Delimiters or Add Block Delimiters).
        ProviderName System\Refactorings\Brace Refactoring is Available

      5. Double-click the ContextProvider to generate an event handler for the ContextSatisfied event. Add this code:

            const string STR_RemoveRedundantBlockDelimiters = "Remove Redundant Block Delimiters";
            const string STR_AddBlockDelimiters = "Add Block Delimiters";
            const string STR_SystemRefactoringIsAvailable = "System\\Refactoring is Available({0})";
            const string STR_Refactor = "Refactor";
        
            private bool RemoveRedundantBlockDelimitersIsAvailable
            {
              get
              {
                return CodeRush.Context.Satisfied(String.Format(STR_SystemRefactoringIsAvailable, STR_RemoveRedundantBlockDelimiters)) == ContextResult.Satisfied;
              }
            }
        
            private bool AddBlockDelimitersIsAvailable
            {
              get
              {
                return CodeRush.Context.Satisfied(String.Format(STR_SystemRefactoringIsAvailable, STR_AddBlockDelimiters)) == ContextResult.Satisfied;
              }
            }
        
            private void ctxBraceRefactoringAvailable_ContextSatisfied(ContextSatisfiedEventArgs ea)
            {
              CodeRush.Context.ClearCache();
              CodeRush.Source.ParseIfTextChanged();
              ea.Satisfied = RemoveRedundantBlockDelimitersIsAvailable || AddBlockDelimitersIsAvailable;
            }
      6. Next, let’s drop an Action onto the design surface. We’re going to create a single command that will apply the brace refactoring that is available.
      7. Fill out the following properties for the new Action:

        Property Value
        Name actSmartBraceRefactoring
        ActionName SmartBraceRefactoring
        Description Adds or removes braces as needed.

      8. Add the following code:

         private void actSmartBraceRefactoring_Execute(ExecuteEventArgs ea)
            {
              if (RemoveRedundantBlockDelimitersIsAvailable)
                CodeRush.Command.Execute(STR_Refactor, STR_RemoveRedundantBlockDelimiters);
              else if (AddBlockDelimitersIsAvailable)
              {
                CodeRush.Command.Execute(STR_Refactor, STR_AddBlockDelimiters);
                CodeRush.Command.Execute("Edit.LineUp");    // Put the caret on the previous line.
                CodeRush.Command.Execute("Edit.LineEnd");    // Put the caret after the opening brace (so Remove Redundant Block Delimiters is immediately available).
              }
            }
      9. Nice. Now, let’s try it out. Run your plug-in to start up a new instance of Visual Studio.

      Now this works like I want it to. If I want to add or remove redundant braces, I simply hit either brace key. If I want to embed a selection in braces, I simply hit either brace key (the key I hit determines the active part of the selection). If I want to add new braces I simply hit either brace key (the key I hit determines whether I want the caret inside the braces or after them). Context differentiates.

      Six different powerful but related features. Two keys. Simple.

      Recent Edits Navigation

      Two of the features I use in Visual Studio frequently are the View.NavigateForward and View.NavigateBackward commands. In my install these keys are bound to Ctrl+- (Ctrl+Minus key) and Ctrl+Shift+- (Ctrl+Shift+Minus key). I want to improve these features in a two small ways:

      1. I want to make them more accessible (improving both discoverability and efficiency) by featuring them prominently on the keyboard, with a dedicated key for each direction.
      2. I want to improve the feature with a LocatorBeacon so your eyes and brain find the target location with less cognitive effort.

      So to do this we need two Actions that will effectively wrap Visual Studio’s view navigation commands. Here are the steps in detail:

      1. Open up the designer for the Plugin1.cs file.
      2. From the Visual Studio Toolbox window, drop an Action onto the Plugin1 design surface.

        DropAnAction
      3. Fill out the following properties for the new Action:

        Property Value
        Name actNavViewBack
        ActionName NavViewBack
        Description Jumps to previous edit points in the code.
      4. Double-click the Action to generate a handler for its Execute event. Add this code to the handler:

         private void actNavViewBack_Execute(ExecuteEventArgs ea)
         {
           DropMarkerIfNecessary();
           showBeaconAfterNextMove = true;
           CodeRush.Command.Execute("View.NavigateBackward");
         }
      5. Drop another Action onto the Plugin1.cs design surface.
      6. Fill out the following properties for this second Action:

        Property Value
        Name actNavViewForward
        ActionName NavViewForward
        Description Jumps to later edit points in the code.
      7. Double-click the Action to generate a handler for its Execute event. Add this code to the handler:

        private void actNavViewForward_Execute(ExecuteEventArgs ea)
        {
          DropMarkerIfNecessary();
          showBeaconAfterNextMove = true;
          CodeRush.Command.Execute("View.NavigateForward");
        }
      8. Activate the Plugin1.cs design surface.
      9. On the Toolbox, find and drop a LocatorBeacon control onto the design surface.

        LocatorBeaconControl

        Tip: Type “Locator” into the search box at the top of the Toolbox.

        The LocatorBeacon control draws those animated circles on the editor when collecting markers, and are useful for bringing your eyes into the right spot (especially when working with large monitors).
      10. Fill out the following properties for this LocatorBeacon:

        Property Value
        (Name)

        locatorBeacon1

        Duration 500

      11. I want this locatorBeacon to be green so it is distinctive. Inside the PlugIn1.cs source file, navigate to the InitializePlugIn method. Add the following line of code to the end of the method:

        public override void InitializePlugIn()
        {
          base.InitializePlugIn();
        
          locatorBeacon1.Color = DevExpress.DXCore.Platform.Drawing.Color.FromArgb(0x41, 0xBF, 0x79);
        }
      12. Activate and then Click the Plugin1.cs design surface. The Properties window should show the main form selected.

        PluginSurfaceActivated

      13. Now click the Events icon. Plugin design surfaces give you access to scores of Visual Studio and CodeRush events. There are two events we want to listen to.
      14. Double-click the CaretMoved event. Add the following code to show our new locatorBeacon when needed:

        private void PlugIn1_CaretMoved(CaretMovedEventArgs ea)
        {
          if (showBeaconAfterNextMove)
          {
            locatorBeacon1.Start(ea.NewPosition.TextView, ea.NewPosition.Line, ea.NewPosition.Offset);
            showBeaconAfterNextMove = false;
          }
          else if (justShowedBeacon)
            justShowedBeacon = false;
          else
            customerMovedCaret = true;
        }
      15. Activate the Plugin1.cs design surface. Make sure the Properties window is listing events.
      16. Double-click the CommandExecuted event. Add the following code to immediately show the locator beacon after either Visual Studio view navigation commands are invoked:

        private void PlugIn1_CommandExecuted(CommandExecutedEventArgs ea)
        {
          if (showBeaconAfterNextMove)
            if (ea.CommandName == "View.NavigateForward" || ea.CommandName == "View.NavigateBackward")
            {
              showBeaconAfterNextMove = false;
              justShowedBeacon = true;
              TextView active = CodeRush.TextViews.Active;
              if (active != null)
                locatorBeacon1.Start(active, active.Caret.Line, active.Caret.Offset);
            }
        }




      Section Navigation

      So at the top left of the keyboard, I have four keys with icons representing member sections for Fields, Methods, Properties, and Events. I want these keys to instantly take me to the corresponding section of the active class. I anticipate this will be useful for creating new classes and also examining and understanding classes built by others. So if I hit the Methods button, it should take me to the start of the first method in the active class. Hitting that key a second time should take me to the end of that group of methods. Hitting the method key a third time should take me to the start of the next group of methods found in the file. Holding down the Shift key and hitting the Methods button should take me in the reverse direction.

      To build this feature, follow these steps:

       
      1. Open up the designer for the Plugin1.cs file.
      2. From the Visual Studio Toolbox window, drop an Action onto the Plugin1 design surface.

        DropAnAction


        DroppedAction
      3. Fill out the following properties for the new Action:


        Property

        Value
        (Name) actSectionJumpNext
        ActionName SectionJumpNext
        Description Jumps to the next specified section (pass the section name as a parameter – can be Methods, Properties, Fields, Events, or Constructors).

      4. Now we need to add a parameter. Double-click the Parameters (Collection)” value to bring up the Parameter Collection Editor.
      5. Click the Add button. Specify the following properties for the parameter and click OK:


        Property

        Value
        Description The section to jump to.
        Name section
        Optional False
        Type String

      6. Good. Now we need another Action to handle the jump back in the reverse direction. To save time, let’s copy this action and paste it back on the plug-in’s design surface. Change the following properties (text in red shows changes from the original Action):


        Property

        Value
        (Name) actSectionJumpPrevious
        ActionName SectionJumpPrevious
        Description Jumps to the previous specified section (pass the section name as a parameter – can be Methods, Properties, Fields, Events, or Constructors).

        The parameter remains the same.
      7. Double-click the actSectionJumpNext Action to generate an event handler for the Execute event. Add this code:

        private void actSectionJumpNext_Execute(ExecuteEventArgs ea)
        {
          var parameter = actSectionJumpNext.Parameters.GetString("section");
          TargetSections targetSection = GetTargetSection(InitialCase(parameter));
        
          JumpToNextSection(targetSection);
        }
      8. Reactivate the Plugin1.cs design surface. Double-click the actSectionJumpPrevious Action to generate an event handler for the Execute event. Add this code:

        private void actSectionJumpPrevious_Execute(ExecuteEventArgs ea)
        {
          var parameter = actSectionJumpPrevious.Parameters.GetString("section");
          TargetSections targetSection = GetTargetSection(InitialCase(parameter));
        
          JumpToPreviousSection(targetSection);
        }
      9. Add the following support code:

        public enum TargetSections
        {
          Unknown,
          Fields,
          Constants,
          Methods,
          Properties,
          Constructors,
          Events,
          Types
        }
        
        static string InitialCase(string targetSection)
        {
          if (targetSection == null || targetSection.Length < 1)
            return targetSection;
          return char.ToUpper(targetSection[0]) + targetSection.Substring(1).ToLower();
        }
        
        static TargetSections GetTargetSection(string targetStr)
        {
          if (targetStr.StartsWith("Field"))
            return TargetSections.Fields;
          else if (targetStr.StartsWith("Method"))
            return TargetSections.Methods;
          else if (targetStr.StartsWith("Constructor"))
            return TargetSections.Constructors;
          else if (targetStr.StartsWith("Event"))
            return TargetSections.Events;
          else if (targetStr.StartsWith("Propert"))
            return TargetSections.Properties;
          else if (targetStr.StartsWith("Const"))
            return TargetSections.Constants;
          else if (targetStr.StartsWith("Type"))
            return TargetSections.Types;
          else
            return TargetSections.Unknown;
        }
        
        static List<LanguageElementType > GetTypesToFind(TargetSections targetSection)
        {
          List<LanguageElementType > typesToFind = new List<LanguageElementType>();
          if (targetSection == TargetSections.Constructors)
            typesToFind.Add(LanguageElementType.Method);
          else if (targetSection == TargetSections.Events)
            typesToFind.Add(LanguageElementType.Event);
          else if (targetSection == TargetSections.Fields)
          {
            typesToFind.Add(LanguageElementType.Variable);
            typesToFind.Add(LanguageElementType.InitializedVariable);
          }
          else if (targetSection == TargetSections.Constants)
            typesToFind.Add(LanguageElementType.Const);
          else if (targetSection == TargetSections.Types)
          {
            typesToFind.Add(LanguageElementType.TypeDeclaration);
            typesToFind.Add(LanguageElementType.Delegate);
            typesToFind.Add(LanguageElementType.Enum);
          }
          else if (targetSection == TargetSections.Methods)
            typesToFind.Add(LanguageElementType.Method);
          else if (targetSection == TargetSections.Properties)
            typesToFind.Add(LanguageElementType.Property);
          return typesToFind;
        }
        
        static void AddRange(List<SourceRange> existingRanges, SourceRange sourceRange, SourcePoint end)
        {
          sourceRange.End = end;
          existingRanges.Add(sourceRange);
        }
        
        static List<SourceRange> GetExistingRanges(TargetSections targetSection, TypeDeclaration activeType)
        {
          bool lookingForConstructor = targetSection == TargetSections.Constructors;
        
          List<LanguageElementType> typesToFind = GetTypesToFind(targetSection);
        
          bool lookingForNextSectionStart = true;
          SourceRange sourceRange = SourceRange.Empty;
          Member lastMatchingMember = null;
          List<SourceRange > existingRanges = new List<SourceRange>();
          foreach (Member member in activeType.AllMembers)
          {
            bool foundMatchingMember = typesToFind.Contains(member.ElementType);
            if (foundMatchingMember && lookingForConstructor)
            {
              Method method = member as Method;
              if (method != null && method.IsConstructor)
                foundMatchingMember = false;
            }
            if (foundMatchingMember)
            {
              lastMatchingMember = member;
              if (lookingForNextSectionStart)
              {
                sourceRange.Start = member.Range.Start;
                lookingForNextSectionStart = false;
              }
            }
            else if (!lookingForNextSectionStart)
            {
              AddRange(existingRanges, sourceRange, lastMatchingMember.Range.End);
        
              lookingForNextSectionStart = true;
              sourceRange = SourceRange.Empty;
              lastMatchingMember = null;
            }
          }
        
          if (!lookingForNextSectionStart)
            AddRange(existingRanges, sourceRange, lastMatchingMember.Range.End);
          return existingRanges;
        }
        
        static SourcePoint GetPreviousTarget(List<SourceRange> existingRanges)
        {
          SourcePoint target = SourcePoint.Empty;
          int activeLine = CodeRush.Caret.SourcePoint.Line;
          bool targetIsInPreviousRange = false;
          for (int i = existingRanges.Count - 1; i >= 0; i--)
          {
            SourceRange thisRange = existingRanges[i];
            if (targetIsInPreviousRange)
              return thisRange.End;
        
            int startLine = thisRange.Start.Line;
            int endLine = thisRange.End.Line;
            if (activeLine == startLine)
              targetIsInPreviousRange = true;
            else if (activeLine <= endLine && activeLine > startLine)
              return thisRange.Start;
          }
        
          if (targetIsInPreviousRange || target == SourcePoint.Empty)
          {
            // We need to loop from the beginning back around to the end...
            if (existingRanges.Count > 0)
              return existingRanges[existingRanges.Count - 1].End;
          }
        
          return target;
        }
        
        static SourcePoint GetNextTarget(List<SourceRange> existingRanges)
        {
          SourcePoint target = SourcePoint.Empty;
          int activeLine = CodeRush.Caret.SourcePoint.Line;
          bool targetIsInNextRange = false;
          foreach (SourceRange existingRange in existingRanges)
          {
            if (targetIsInNextRange)
              return existingRange.Start;
        
            int startLine = existingRange.Start.Line;
            int endLine = existingRange.End.Line;
        
            if (activeLine == endLine)
              targetIsInNextRange = true;
            else if (activeLine >= startLine && activeLine < endLine)
              return existingRange.End;
          }
        
          if (targetIsInNextRange || target == SourcePoint.Empty)
          {
            // We need to loop back around to the beginning...
            if (existingRanges.Count > 0)
              return existingRanges[0].Start;
          }
        
          return target;
        }
        
        void SectionJump(SourcePoint target)
        {
          if (target != SourcePoint.Empty)
          {
            DropMarkerIfNecessary();
            showBeaconAfterNextMove = true;
            CodeRush.Caret.MoveTo(target);
          }
        }
        
        void JumpToNextSection(TargetSections targetSection)
        {
          TypeDeclaration activeType = CodeRush.Source.ActiveType as TypeDeclaration;
          if (activeType == null)
            return;
        
          SectionJump(GetNextTarget(GetExistingRanges(targetSection, activeType)));
        }
        
        
        void JumpToPreviousSection(TargetSections targetSection)
        {
          TypeDeclaration activeType = CodeRush.Source.ActiveType as TypeDeclaration;
          if (activeType == null)
            return;
        
          SectionJump(GetPreviousTarget(GetExistingRanges(targetSection, activeType)));
        }

      This code is a bit sophisticated. It first collects the ranges of member groups inside the current class. So for example, there may be several groups of methods in a class – maybe organized by visibility, instance/static, functionality, or perhaps not organized at all. For the purposes of this code, a member range is defined as the distance between the start and end of a single member (or a group of two or more adjacent members of the same type). After collecting all those ranges, it then calculates the next position based on the desired movement direction (Previous or Next) and also based on the current position. Wrapping from the end of the last group back to the beginning of the first group is supported.

      Bonus Actions

      There are two more Actions I’ve added to the plug-in. One, called ShowURL, displays the specified web site inside the Visual Studio browser as a document.

      The other, SendKeys, will send the keys in the specified key string to the active window. Key strings can contain individual characters (e.g., a-z, A-Z, 0-9, punctuation, etc.), and can also optionally include any of the elements of the Keys enum (placed in square brackets). For example, “// Hello World[Enter]”.

      Last Minute Request – The Pizza Key

      I’m not sure why this is, but often I get requests that some might consider crazy (or surely a joke). For example, this comment came in yesterday:

      WhereIsThePizzaKey

      Great question, Josh! Well, as I said from the beginning, this was always expected to be a work in progress – something I would refine over time. And thanks to you, my keyboard now has a pizza key:

      ThePizzaKey

      The pizza key now replaces the Events icon since it was unlikely to get frequent use. If you want to print this out, here’s the image:

      PizzaKeyTemplate
      (click picture above to get to full size image)

      The first time you press the Pizza key, you will see this window:

      NomNom

      There are very likely some locale issues here in the Quick Links to food chains that may not be in your area, however you can get full source to this plug-in and change those Quick Links if you like. And there are always the Find buttons…. Ultimately, when you decide upon a place you want, you can specify its online ordering URL in the textbox here and always have it only a single click away.

      Yummy!

      Tomorrow

      OK, that’s it for today. Next time we’ll wrap up all the feature shortcut binding and complete the series.

    1 2
    3
    4 5 6 7 8 9 10
    LIVE CHAT

    Chat is one of the many ways you can contact members of the DevExpress Team.
    We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

    If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

    FOLLOW US

    DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

    Copyright © 1998-2017 Developer Express Inc.
    All trademarks or registered trademarks are property of their respective owners