Click here to Skip to main content
15,891,204 members
Articles / Desktop Programming / WPF

AvalonDock and MVVM

Rate me:
Please Sign up or sign in to vote.
4.87/5 (50 votes)
9 Oct 2011CPOL34 min read 214.8K   9.2K   135  
Demonstrates a technique for integrating AvalonDock with an MVVM application.
using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;

namespace SampleApp.ViewModels
{
    /// <summary>
    /// Class for the main window's view-model.
    /// </summary>
    public sealed class MainWindowViewModel : AbstractViewModel
    {
        /// <summary>
        /// The default title for the main window.
        /// </summary>
        private static readonly string defaultTitle = "My First Text Editor";

        /// <summary>
        /// View-model for the active document.
        /// </summary>
        private TextFileDocumentViewModel activeDocument = null;

        /// <summary>
        /// View-model for the active pane.
        /// </summary>
        private AbstractPaneViewModel activePane = null;

        public MainWindowViewModel(IDialogProvider dialogProvider)
        {
            this.DialogProvider = dialogProvider;

            //
            // Initialize the 'Document Overview' pane view-model.
            //
            this.DocumentOverviewPaneViewModel = new DocumentOverviewPaneViewModel(this);

            //
            // Initialize the 'Open Documents' pane view-model.
            //
            this.OpenDocumentsPaneViewModel = new OpenDocumentsPaneViewModel(this);

            //
            // Add view-models for panes to the 'Panes' collection.
            //
            this.Panes = new ObservableCollection<AbstractPaneViewModel>();
            this.Panes.Add(this.DocumentOverviewPaneViewModel);
            this.Panes.Add(this.OpenDocumentsPaneViewModel);

            //
            // Add an example/test document view-model.
            //
            this.Documents = new ObservableCollection<TextFileDocumentViewModel>();
            this.Documents.Add(new TextFileDocumentViewModel(string.Empty, "test data!", true));
        }

        /// <summary>
        /// The current title of the main window.
        /// </summary>
        public string Title
        {
            get
            {
                var title = new StringBuilder();
                title.Append(defaultTitle);

                if (this.ActiveDocument != null)
                {
                    title.Append(" - ");
                    title.Append(this.ActiveDocument.Title);
                }

                return title.ToString();
            }
        }

        /// <summary>
        /// View-models for panes.
        /// </summary>
        public ObservableCollection<AbstractPaneViewModel> Panes
        {
            get;
            private set;
        }

        /// <summary>
        /// View-models for documents.
        /// </summary>
        public ObservableCollection<TextFileDocumentViewModel> Documents
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns 'true' if any of the open documents are modified.
        /// </summary>
        public bool AnyDocumentIsModified
        {
            get
            {
                foreach (var document in this.Documents)
                {
                    if (document.IsModified)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// View-model for the active document.
        /// </summary>
        public TextFileDocumentViewModel ActiveDocument
        {
            get
            {
                return activeDocument;
            }
            set
            {
                if (activeDocument == value)
                {
                    return;
                }

                if (activeDocument != null)
                {
                    activeDocument.IsModifiedChanged -= new EventHandler<EventArgs>(activeDocument_IsModifiedChanged);
                }

                activeDocument = value;

                if (activeDocument != null)
                {
                    activeDocument.IsModifiedChanged += new EventHandler<EventArgs>(activeDocument_IsModifiedChanged);
                }

                OnPropertyChanged("ActiveDocument");
                OnPropertyChanged("Title");

                if (ActiveDocumentChanged != null)
                {
                    ActiveDocumentChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Event raised when the ActiveDocument property has changed.
        /// </summary>
        public event EventHandler<EventArgs> ActiveDocumentChanged;

        /// <summary>
        /// View-model for the active pane.
        /// </summary>
        public AbstractPaneViewModel ActivePane
        {
            get
            {
                return activePane;
            }
            set
            {
                if (activePane == value)
                {
                    return;
                }

                activePane = value;

                OnPropertyChanged("ActivePane");
            }
        }

        /// <summary>
        /// View-model for the 'Document Overview' pane.
        /// </summary>
        public DocumentOverviewPaneViewModel DocumentOverviewPaneViewModel
        {
            get;
            private set;
        }

        /// <summary>
        /// View-model for the 'Open Documents' pane.
        /// </summary>
        public OpenDocumentsPaneViewModel OpenDocumentsPaneViewModel
        {
            get;
            private set;
        }

        /// <summary>
        /// Create a new file and add it to the view-model.
        /// </summary>
        public void NewFile()
        {
            var newDocument = new TextFileDocumentViewModel(string.Empty, string.Empty, false);
            this.Documents.Add(newDocument);
        }

        /// <summary>
        /// Allow the user to select a particular file then open it.
        /// </summary>
        public void OpenFile()
        {
            string filePath = null;
            if (DialogProvider.UserSelectsFileToOpen(out filePath))
            {
                OpenFile(filePath);
            }
        }

        /// <summary>
        /// Open a file, create a view-model and it to the collection of document view-models.
        /// </summary>
        public void OpenFile(string filePath)
        {
            try
            {
                var fileContent = File.ReadAllText(filePath);
                var newDocumentViewModel = new TextFileDocumentViewModel(filePath, fileContent, false);
                this.Documents.Add(newDocumentViewModel);
            }
            catch (Exception ex)
            {
                DialogProvider.ErrorMessage("Failed to open document " + filePath + "\n" +
                                            "Exception occurred:\n" +
                                            ex.ToString());
            	
            }
        }

        /// <summary>
        /// Save the active document, allowing the user to select a filename for new files.
        /// </summary>
        public void SaveFile()
        {
            SaveFile(this.ActiveDocument);
        }

        /// <summary>
        /// Save the specified document, allowing the user to select a filename for new files.
        /// </summary>
        public void SaveFile(TextFileDocumentViewModel document)
        {
            if (string.IsNullOrEmpty(document.FilePath))
            {
                SaveFileAs(document);
            }
            else
            {
                SaveFile(document, document.FilePath);
            }
        }

        /// <summary>
        /// Save the specified document to the specified filepath.
        /// </summary>
        public void SaveFile(TextFileDocumentViewModel document, string newFilePath)
        {
            try
            {
                File.WriteAllText(newFilePath, document.Text);

                document.FilePath = newFilePath;
                document.IsModified = false;
            }
            catch (Exception ex)
            {
                DialogProvider.ErrorMessage("Failed to save document " + newFilePath + "\n" +
                                            "Exception occurred: \n" +
                                            ex.ToString());                                          
            }
        }

        /// <summary>
        /// Save the active document as a new file, allowing the user to specify the new filepath.
        /// </summary>
        public void SaveFileAs()
        {
            SaveFileAs(this.ActiveDocument);
        }

        /// <summary>
        /// Save the specified document as a new file, allowing the user to specify the new filepath.
        /// </summary>
        public void SaveFileAs(TextFileDocumentViewModel document)
        {
            string newFilePath = null;
            if (DialogProvider.UserSelectsNewFilePath(document.FilePath, out newFilePath))
            {
                SaveFile(document, newFilePath);
            }
        }

        /// <summary>
        /// Save all documents.
        /// </summary>
        public void SaveAllFiles()
        {
            foreach (var document in this.Documents)
            {
                SaveFile(document);
            }
        }

        /// <summary>
        /// Close the active document.
        /// </summary>
        public void CloseFile()
        {
            CloseFile(this.ActiveDocument);
        }

        /// <summary>
        /// Determine if the file can be closed.
        /// If the file is modified, but not saved, the user is asked
        /// to confirm that the document should be closed.
        /// </summary>
        public bool QueryCanCloseFile(TextFileDocumentViewModel document)
        {
            if (document.IsModified)
            {
                //
                // Ask the user to confirm closing a modified document.
                //
                if (!this.DialogProvider.QueryCloseModifiedDocument(document))
                {
                    // User doesn't want to close it.
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Close the specified document.
        /// Returns 'true' if the user allowed the file to be closed,
        /// or 'false' if the user canceled closing of the file.
        /// </summary>
        public bool CloseFile(TextFileDocumentViewModel document)
        {
            if (!QueryCanCloseFile(document))
            {
                //
                // User has chosen not to close the file.
                //
                return false;
            }

            this.Documents.Remove(document);

            //
            // File has been closed.
            //
            return true;
        }

        /// <summary>
        /// Close all open documents.
        /// </summary>
        public void CloseAllFiles()
        {
            //
            // Copy the list to an array so that we can iterate
            // and remove items from the list.
            //
            var documents = this.Documents.ToArray();

            foreach (var document in documents)
            {
                CloseFile(document);
            }
        }

        /// <summary>
        /// Show all panes.
        /// </summary>
        public void ShowAllPanes()
        {
            foreach (var pane in this.Panes)
            {
                pane.IsVisible = true;
            }
        }

        /// <summary>
        /// Hide all panes.
        /// </summary>
        public void HideAllPanes()
        {
            foreach (var pane in this.Panes)
            {
                pane.IsVisible = false;
            }
        }

        /// <summary>
        /// Called when the application is closing.
        /// Return 'true' to allow application to exit.
        /// </summary>
        public bool OnApplicationClosing()
        {
            if (this.AnyDocumentIsModified)
            {
                if (!this.DialogProvider.QueryCloseApplicationWhenDocumentsModified())
                {
                    //
                    // User has cancelled application exit.
                    //
                    return false;
                }
            }

            //
            // Allow application exit to proceed.
            //
            return true;
        }

        /// <summary>
        /// An interface that provides dialog services, eg open file, save file as, error message boxes.
        /// </summary>
        private IDialogProvider DialogProvider
        {
            get;
            set;
        }

        /// <summary>
        /// Event raised when the active document's IsModified property has changed.
        /// </summary>
        private void activeDocument_IsModifiedChanged(object sender, EventArgs e)
        {
            //
            // Update the main window's title when the active document has been modified.
            //
            OnPropertyChanged("Title");
        }
    }
}

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
Chief Technology Officer
Australia Australia
Software craftsman | Author | Writing rapidfullstackdevelopment.com - Posting about how to survive and flourish as a software developer

Follow on Twitter for news and updates: https://twitter.com/codecapers

I'm writing a new book: Rapid Fullstack Development. Learn from my years of experience and become a better developer.

My second book, Bootstrapping Microservices, is a practical and project-based guide to building distributed applications with microservices.

My first book Data Wrangling with JavaScript is a comprehensive overview of working with data in JavaScript.

Data-Forge Notebook is my notebook-style application for data transformation, analysis and transformation in JavaScript.

I have a long history in software development with many years in apps, web apps, backends, serious games, simulations and VR. Making technology work for business is what I do: building bespoke software solutions that span multiple platforms.

I have years of experience managing development teams, preparing technical strategies and creation of software products. I can explain complicated technology to senior management. I have delivered cutting-edge products in fast-paced and high-pressure environments. I know how to focus and prioritize to get the important things done.

Author

- Rapid Fullstack Development
- Bootstrapping Microservices
- Data Wrangling with JavaScript

Creator of Market Wizard

- https://www.market-wizard.com.au/

Creator of Data-Forge and Data-Forge Notebook

- http://www.data-forge-js.com
- http://www.data-forge-notebook.com

Web

- www.codecapers.com.au

Open source

- https://github.com/ashleydavis
- https://github.com/data-forge
- https://github.com/data-forge-notebook


Skills

- Quickly building MVPs for startups
- Understanding how to get the most out of technology for business
- Developing technical strategies
- Management and coaching of teams & projects
- Microservices, devops, mobile and fullstack software development

Comments and Discussions