Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / WPF

PlantUML Editor: A Fast and Simple UML Editor using WPF

Rate me:
Please Sign up or sign in to vote.
4.98/5 (65 votes)
11 Jun 2011CPOL16 min read 233.6K   6.4K   233  
A WPF smart client to generate UML diagrams from plain text using plantuml tool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using PlantUmlEditor.Model;
using System.Diagnostics;
using PlantUmlEditor.Properties;
using System.IO;
using System.ComponentModel;
using Utilities;

namespace PlantUmlEditor
{
    /// <summary>
    /// Takes a DiagramFile object into DataContext and renders the text editor and 
    /// shows the generated diagram
    /// </summary>
    public partial class DiagramViewControl : UserControl
    {
        private WeakReference<MenuItem> _LastMenuItemClicked = default(WeakReference<MenuItem>);

        public DiagramViewControl()
        {
            InitializeComponent();

            foreach (MenuItem topLevelMenu in AddContextMenu.Items)
            {
                foreach (MenuItem itemMenu in topLevelMenu.Items)
                {
                    itemMenu.Click += new RoutedEventHandler(MenuItem_Click);
                }
            }
        }

        public event Action<DiagramFile> OnBeforeSave;
        public event Action<DiagramFile> OnAfterSave;
        public event Action<DiagramFile> OnClose;
        
        private DiagramFile CurrentDiagram
        {
            get
            {
                return this.DataContext as DiagramFile;
            }
        }

        private void SaveDiagram_Click(object sender, RoutedEventArgs e)
        {
            this.SaveAndRefreshDiagram();
        }

        private void SaveAndRefreshDiagram()
        {
            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            if (this.CurrentDiagram == default(DiagramFile))
                return;

            var diagramFileName = this.CurrentDiagram.DiagramFilePath;
            var pathForContentEditor = ContentEditor.Tag as string;

            if (diagramFileName != pathForContentEditor)
            {
                MessageBox.Show(Window.GetWindow(this), 
                    "Aha! This is one of those weird race condition I am getting into." + Environment.NewLine
                    + "Close the app and start again", "Weird race condition", 
                    MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var content = ContentEditor.Text; 
            this.CurrentDiagram.Content = content;

            OnBeforeSave(this.CurrentDiagram);

            string plantUmlPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Thirdparty\\plantuml.exe");
            if (!File.Exists(plantUmlPath))
            {
                MessageBox.Show(Window.GetWindow(this), 
                    "Cannot find file: " + Environment.NewLine
                    + plantUmlPath, "PlantUml.exe not found", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            BackgroundWork.WaitForAllWork(TimeSpan.FromSeconds(20));
            BackgroundWork.DoWork(
                () =>
                {
                    // Save the diagram content
                    File.WriteAllText(diagramFileName, content);

                    // Use plantuml to generate the graph again                    
                    using (var process = new Process())
                    {
                        var startInfo = new ProcessStartInfo();
                        startInfo.FileName = plantUmlPath;
                        startInfo.Arguments = "\"" + diagramFileName + "\"";
                        startInfo.WindowStyle = ProcessWindowStyle.Hidden; // OMAR: Trick #5
                        startInfo.CreateNoWindow = true; // OMAR: Trick #5
                        process.StartInfo = startInfo;
                        if (process.Start())
                        {
                            process.WaitForExit(10000);
                        }
                    }
                },
                () =>
                {
                    BindingOperations.GetBindingExpression(DiagramImage, Image.SourceProperty).UpdateTarget();

                    OnAfterSave(this.CurrentDiagram);
                },
                (exception) =>
                {
                    OnAfterSave(this.CurrentDiagram);
                    MessageBox.Show(Window.GetWindow(this), exception.Message, "Error running PlantUml",
                                    MessageBoxButton.OK, MessageBoxImage.Error);
                });
        }

        private void CloseDiagram_Click(object sender, RoutedEventArgs e)
        {
            OnClose(this.CurrentDiagram);
        }

        private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                var newDiagram = (e.NewValue as DiagramFile);
                ContentEditor.Text = newDiagram.Content;
                ContentEditor.Tag = newDiagram.DiagramFilePath;
            }            

            if (this._LastMenuItemClicked != default(WeakReference<MenuItem>))
            {
                this._LastMenuItemClicked.Dispose();
                this._LastMenuItemClicked = null;
            }
        }

        private void ContentEditor_TextChanged(object sender, EventArgs e)
        {
            if (AutoRefreshCheckbox.IsChecked.Value)
            {
                if (!BackgroundWork.IsAnyWorkRunning())
                {
                    BackgroundWork.DoWorkAfter(SaveAndRefreshDiagram, 
                                               TimeSpan.FromSeconds(
                                                   int.Parse(RefreshSecondsTextBox.Text)));
                }
            }
        }

        private void AddStuff_Click(object sender, RoutedEventArgs e)
        {
            // Trick: Open the context menu automatically whenever user
            // clicks the "Add" button
            AddContextMenu.IsOpen = true;

            // If user last added a particular diagram items, say Use case
            // item, then auto open the usecase menu so that user does not
            // have to click on use case again. Saves time when you are adding
            // a lot of items for the same diagram
            if (_LastMenuItemClicked != default(WeakReference<MenuItem>))
            {
                MenuItem parentMenu = (_LastMenuItemClicked.Target.Parent as MenuItem);
                parentMenu.IsSubmenuOpen = true;
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            this._LastMenuItemClicked = e.Source as MenuItem;
            this.AddCode((e.Source as MenuItem).Tag as string);
        }

        private void AddCode(string code)
        {
            ContentEditor.SelectionLength = 0;

            var formattedCode = code.Replace("\\r", Environment.NewLine) 
                + Environment.NewLine
                + Environment.NewLine;

            Clipboard.SetText(formattedCode);
            ContentEditor.Paste();

            this.SaveAndRefreshDiagram();
        }

        private void CopyToClipboard_Click(object sender, RoutedEventArgs e)
        {            
            Clipboard.SetImage(DiagramImage.Source as BitmapSource);
        }

        private void OpenInExplorer_Click(object sender, RoutedEventArgs e)
        {
            Process
                .Start("explorer.exe","/select," + this.CurrentDiagram.ImageFilePath)
                .Dispose();
        }

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

Comments and Discussions