Click here to Skip to main content
15,884,388 members
Articles / Desktop Programming / WPF

A (Mostly) Declarative Framework for Building Simple WPF-based Wizards

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
7 Mar 2011LGPL322 min read 19.3K   229   15  
A declarative framework for building WPF wizards.
/*
* Olbert.Utilities.Misc
* miscellaneous items for simplifying .NET programming
* Copyright (C) 2011  Mark A. Olbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published 
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Collections;
using System.Text.RegularExpressions;
using System.IO;
using System.Data.SqlClient;

namespace Olbert.Utilities
{
    /// <summary>
    /// a class, derived from ResourceTextFile, representing an sql command file in an SourceAssembly.
    /// <para>The class is enumerable, with each element representing a single section in the
    /// underlying sql command file. A 'section' ends with the sql keyword 'GO' (case-insensitive)
    /// or the end of the file.</para>
    /// </summary>
    public class ResourceSqlFile : ResourceTextFile, IEnumerable<string>
    {
        private List<string> segments;
        private bool loadingFile = false;

        /// <summary>
        /// Initializes an instance and assigns it to a ResourceDirectory object. Content from the underlying
        /// resource is not loaded when the instance is created. Loading from the underlying resource occurs 
        /// when the content is accessed.
        /// </summary>
        /// <param name="parent">the ResourceDirectory to which the object belongs</param>
        /// <param name="name">the name of the ResourceFile, which must be unique within the
        /// owning ResourceDirectory</param>
        public ResourceSqlFile( ResourceDirectory parent, string name )
            :base(parent, name)
        {
        }

        /// <summary>
        /// Initializes an instance and assigns it to a ResourceDirectory object. The Stream containing the contents
        /// of the underlying resource will be used when the content is accessed. That Stream does not have to
        /// be directly tied to an underlying resource. For example, it could be a MemoryStream derived from
        /// decompressing a zipped resource.
        /// </summary>
        /// <param name="parent">the ResourceDirectory to which the object belongs</param>
        /// <param name="name">the name of the ResourceFile, which must be unique within the
        /// owning ResourceDirectory</param>
        /// <param name="dataStream">a stream containing the contents of the object</param>
        public ResourceSqlFile( ResourceDirectory parent, string name, Stream dataStream )
            : base(parent, name, dataStream)
        {
        }

        /// <summary>
        /// Gets the list of command segments in the underlying sql file. A command segment is defined as
        /// a series of lines ended by the sql keyword 'GO' (case-insensitive) or the end of the file.
        /// </summary>
        public List<string> Segments 
        {
            get
            {
                // if segments is not defined we haven't loaded the contents yet,
                // so do so
                if( segments == null )
                {
                    segments = new List<string>();

                    // avoid infinite recursions...
                    if( !loadingFile ) LoadFile();
                }

                return segments;
            }
        }

        /// <summary>
        /// Gets the command segment at the specified index location. A command segment is defined as
        /// a series of lines ended by the sql keyword 'GO' (case-insensitive) or the end of the file.
        /// </summary>
        /// <param name="idx">the index location</param>
        /// <returns>the command segment at the specified index location.</returns>
        public string this[int idx]
        {
            get
            {
                if( ( idx < 0 ) || ( idx >= Segments.Count ) ) return String.Empty;

                return Segments[idx];
            }
        }

        /// <summary>
        /// Executes the contents of the instance on the supplied SqlConnection
        /// </summary>
        /// <param name="conn">the SqlConnection on which to run the command file</param>
        /// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
        /// if you want the method to handle the commit/rollback process internally.</param>
        /// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to 
        /// allow the exception to propagate</param>
        /// <returns>true if the execution succeeded, false if the supplied connection is
        /// undefined or if the execution failed</returns>
        public bool Execute( SqlConnection conn, SqlTransaction trans = null, bool suppressExceptions = true )
        {
            if( conn == null ) return false;
            
            return conn.ExecuteNonQuery(TextContent, trans, suppressExceptions);
        }

        /// <summary>
        /// Executes the contents of the instance segment by segment on the supplied SqlConnection.
        /// A segment is a sequence of T-SQL statements terminated by a GO command (case-insensitive)
        /// </summary>
        /// <param name="conn">the SqlConnection on which to run the command file</param>
        /// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
        /// if you want the method to handle the commit/rollback process internally.</param>
        /// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to 
        /// allow the exception to propagate</param>
        /// <returns>true if all the segments were executed successfully, false if the supplied 
        /// connection is undefined or if any segment execution failed (execution stops on the
        /// first segment failure).</returns>
        public bool ExecuteSegments( SqlConnection conn, SqlTransaction trans = null, bool suppressExceptions = true )
        {
            if( conn == null ) return false;

            for( int idx = 0; idx < Segments.Count; idx++ )
            {
                if( !conn.ExecuteNonQuery(this[idx], trans, suppressExceptions) )
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Executes the contents of the specified segment on the supplied SqlConnection
        /// </summary>
        /// <param name="conn">the SqlConnection on which to run the command file</param>
        /// <param name="segmentNumber">the number of the segment to run</param>
        /// <param name="trans">an SqlTransaction object to manage commits and rollbacks, or null
        /// if you want the method to handle the commit/rollback process internally.</param>
        /// <param name="suppressExceptions">true (default) to hide exceptions and return false, false to 
        /// allow the exception to propagate</param>
        /// <returns>true if the execution succeeded, false if the supplied connection is
        /// undefined, the segment number is invalid or the execution failed</returns>
        public bool Execute( SqlConnection conn, int segmentNumber, SqlTransaction trans = null, bool suppressExceptions = true )
        {
            if( conn == null ) return false;
            if( ( segmentNumber < 0 ) || ( segmentNumber >= Segments.Count ) )
                return false;

            return conn.ExecuteNonQuery(this[segmentNumber], trans, suppressExceptions);
        }

        /// <summary>
        /// Overrides the base class implementation to read the contents of the underlying
        /// resource and extract whatever command segments it contains.
        /// </summary>
        protected override void LoadFile()
        {
            loadingFile = true; // guard flag to avoid infinite recursions

            StringBuilder segBuilder = new StringBuilder();
            StringBuilder builder = new StringBuilder();

            ResetStream();

            string curLine;

            while( !StreamReader.EndOfStream )
            {
                // read the line and add it to the segment builder, as well
                // as the overall content builder. However, suppress adding
                // initial blank lines to a new segment
                curLine = StreamReader.ReadLine();

                if( ( segBuilder.Length > 0 ) || ( curLine.Trim().Length > 0 ) )
                {
                    // if curLine is GO we've reached the end of the current segment,
                    // so add it to the list of segments and reset the segment builder
                    // note that the GO line is not included in the segment. also,
                    // we have to guard against back to back gos, which have the
                    // potential for creating blank segments...which are a no no
                    // when executing command text against a sql server
                    if( String.Compare(curLine, "go", true) == 0 )
                    {
                        if( segBuilder.Length > 0 )
                        {
                            Segments.Add(segBuilder.ToString());

                            segBuilder.Clear();
                        }
                    }
                    else segBuilder.AppendLine(curLine);
                }

                builder.AppendLine(curLine);
            }

            // don't forget the last pending segment if it's not empty
            if( segBuilder.Length > 0 ) Segments.Add(segBuilder.ToString());

            // store the contents of the entire file in the TextContents property
            TextContent = builder.ToString();

            loadingFile = false; // reset guard flag
        }

        /// <summary>
        /// Gets an enumerator for walking through the command segments
        /// </summary>
        /// <returns></returns>
        public IEnumerator<string> GetEnumerator()
        {
            return Segments.GetEnumerator();
        }

        /// <summary>
        /// Gets an enumerator for walking through the command segments
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return Segments.GetEnumerator();
        }
    }
}

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 GNU Lesser General Public License (LGPLv3)


Written By
Jump for Joy Software
United States United States
Some people like to do crossword puzzles to hone their problem-solving skills. Me, I like to write software for the same reason.

A few years back I passed my 50th anniversary of programming. I believe that means it's officially more than a hobby or pastime. In fact, it may qualify as an addiction Smile | :) .

I mostly work in C# and Windows. But I also play around with Linux (mostly Debian on Raspberry Pis) and Python.

Comments and Discussions