Compare Excel, Word and PDF Files in SharePoint

Now I want to show, how you can compare Office documents stored in SharePoint. Sometimes you need to see the differences of two documents that maybe having the same name and are on two different places in SharePoint. Or if version control is enabled on your site and you want to see what recently has been changed in your document.

How Office Documents can be compared?

I’m using WinMerge in combination with xdocdiff Plugin. I have already written something about the topic in my previous post:

http://bernhardelbl.wordpress.com/2012/04/05/compare-excel-word-and-pdf-files-in-visual-studio-team-foundation-server-2010/

Please, read this before you continue here. Especially capter 1 and 2 are important.

Using WinMerge with SharePoint

It is not possible to insert an url in WinMerge to compare documents directly. Of course you can compare the documents in WinMerge by downloading the files to your local machine and compare them using WinMerge manually.

Saving the files locally and comparing them is a time consuming task. If you need that just a few times in the year, the procedure is ok. If you want to do this more often, you can use my tool.

Sharepoint File Compare – The Tool

The tool can compare Office documents in SharePoint more comfortable.

Features:

  • Enter the url directly into the UI
  • Displays the versions of the SharePoint file
  • Enables you to select a version by double-click on the grid
  • Downloads the files and opens WinMerge to compare them automatically

Software Requirements:

How to use the Tool

The tool is just a single dialog where you can enter the files you want to compare.

1. Download Sharepoint File Compare

You can download Sharepoint File Compare from here…

http://dl.dropbox.com/u/40751518/CodeSamples/SharepointFileCompare-0.8.zip

2. Start Sharepoint File Compare

The first time you need to choose the location of WinMergeU.exe. The default location of WinMerge is:
"C:\Program Files\WinMerge\WinMergeU.exe"
You can change the path to WinMergeU.exe every time later. On the bottom right of the main window you will find this button.

3. Comparing two Files

3.1 Insert the Left

First you should enter the first SharePoint url to the Left text box. Version history is loaded automatically and shown in the grid.

3.2 Select the Right from Version History

Double-click on a version history item will select use the item for Right text box.

3.2 Insert the Right

You do not need the version history, you can add every file url manually in the Right text box.

3.4 Click Compare

Downloads the files locally and opens WinMerge.

Interesting Notes for Developers

The source code is included in the download above. It contains very interesting things for starters and advanced developers.

  • Web Service Authentication using NetworkCredential done with background threading and synchronization.
  • CredUIPromptForWindowsCredentials pinvoke sample to show the windows built-in security dialog
  • A very basic MVVM implementation without using a framework
  • Anchor and docking using WPF
  • Asynchronous operations with multi-threading using System.Threading.Tasks namespace and an very interesting (background) Activity class implementation that simplifies background threading and synchronization in WPF.
  • SharePoint Web Services usage is shown: Webs.asmx, Versions.asmx

Maybe for some of this points I will write another blog posts from a more technical perspective.

Sheet Switch AddIn for Excel 2010 – Switch between a lot of Sheets

Office 2010 Only – Please note that the solution here works only with Office 2010

Last year I worked in a specification team for a few month and did a lot in Office Word and Excel. We had one Excel File that contained about 100 Sheets. Switching between the sheets was a very time consuming task. Excel does not offer a comfortable way to navigate, when not all Sheets can be displayed in the navigation bar on the bottom of the Excel window.

Let’s come to look closer, what I mean

Only 5 Sheets are visibile all others are hidden. You must scroll to reach them.

Right-Click to the Sheet-Scrollbar shows the following screen

Using this feature you can switch between the first 15 sheets. This way is acceptable, if you don’t have more that 15 Sheets in your Workbook. Let’s take a more closer look to the “More Sheets…” Option here.

You can see only 12 Sheets by default. To reach more you need to scroll down. The dialog is not realizable. This feature totally useless.

As you can see Excel does not provide a way to switch comfortable between more that 15 Sheets in a Workbook. So I decided to create my own solution.

Excel Sheet Switch

The Add In displays a multi-line switch bar on the bottom of the Excel application’s window, if the Workbook contains more that 5 Sheets.

Download Setup and Source

Office 2010 Only – Please note that the solution here works only with Office 2010

http://dl.dropbox.com/u/40751518/CodeSamples/Be.ExcelSheetSwitch-1.0.0.4.zip

Install the AddIn using Setup

You can use the setup.exe included in the download. Please ignore the security warning and click install – I’m using a Test Certificate there.

Technical Solution

I used Visual Studio 2010 to create a VSTO solution. It tooks me 15 minutes to write the code needed. It’s very simple, so I don’t want to go into details. If you want to know more, you can discover the code in the download above.

HOWTO: Detect design mode in WPF, ASP .NET and Windows Forms applications

If you want to detect the design mode in code, I’m using the following method, that is working fine for WPF, ASP .NET and Windows Forms applications. It’s easy, stable, bug free and has a very good performance.

static class Util
{
    /// <summary>
    /// Contains true, if we are in design mode of Visual Studio
    /// </summary>
    private static bool _designMode;

    /// <summary>
    /// Initializes an instance of Util class
    /// </summary>
    static Util()
    {
        // design mode is true if host process is: Visual Studio, Visual Studio Express Versions (C#, VB, C++) or SharpDevelop
        var designerHosts = new List<string>() { "devenv", "vcsexpress", "vbexpress", "vcexpress", "sharpdevelop" };
        var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName.ToLower();
        _designMode = designerHosts.Contains(processName);
    }

    /// <summary>
    /// Gets true, if we are in design mode of Visual Studio etc..
    /// </summary>
    public static bool DesignMode
    {
        get
        {
            return _designMode;
        }
    }
}

I always had troubles by using the built-in methods of .NET Framework like the System.ComponentModel.Component.DesignMode property. For ASP.NET and WPF there exist other methods to determine the design mode. For me the above one replaces all others and works like a charm.

RibbonGenerator for Visual Studio 2010 Express Editions: Windows Ribbon for WinForms

Yes, RibbonGenerator is also working with Express Editions of Visual Studio 2010, but in version 2.6 of the Ribbon library you need some extra effort to bring the tool up to work.

Requirements:

  • Windows 7 SDK
  • Visual  C++ 2010 Express
  • Visual C# or Basic 2010 Express

Note: You must have Visual C++ 2010 Express Edition installed because it contains the link.exe file that is need to generate .ribbon files.

Manual Registration of Custom Tool

Because the RibbonGenerator setup is not included in compiled format and because the Express Editions does not support setup projects, it is necessary to register the custom tool manually.

  1. Start the “.\RibbonLib_v2.6\RibbonGenerator\RibbonGenerator.sln” solution and ignore all errors
  2. Compile it
  3. Install the RibbonGenerator.dll into the GAC using gacutil.exe in Visual Studio 2010 Command Prompt (Run as Administrator)
    gacutil – i RibbonGenerator.dll
  4. Register the custom tool in Express Edition of Visual Studio 2010. The simplest way is to modify the existing .reg in the RibbonGenerator source folder. Choose the x64 reg file if you have a 64bit operating system. .\RibbonGenerator\RegisterCustomTool_VS2010.reg
    .\RibbonGenerator\RegisterCustomTool_VS2010_x64.reg
    Let take a look into the second one
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\CLSID\{B64582D9-A489-42F4-BA55-BB6039D82916}]
@="RibbonGenerator"
"InprocServer32"="C:\\WINDOWS\\system32\\mscoree.dll"
"Class"="RibbonGenerator.CustomTool"
"Assembly"="RibbonGenerator, Version=1.0.1.0, Culture=neutral, PublicKeyToken=3cf107c5d7e68b1c"
"ThreadingModel"="Both"

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\RibbonGenerator]
@="Ribbon Generator"
"CLSID"="{B64582D9-A489-42F4-BA55-BB6039D82916}"
"GeneratesDesignTimeSource"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Generators\{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}\RibbonGenerator]
@="Ribbon Generator"
"CLSID"="{B64582D9-A489-42F4-BA55-BB6039D82916}"
"GeneratesDesignTimeSource"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\RibbonGenerator]
@="Ribbon Generator"
"CLSID"="{B64582D9-A489-42F4-BA55-BB6039D82916}"
"GeneratesDesignTimeSource"=dword:00000001

All we have to do now, is to place the correct keys for Express Editions

For Visual Studio 2010 (64bit Operating System)

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0

For Visual C# 2010 Express (64bit Operating System)

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VCSExpress\10.0

For Visual Basic 2010 Express (64bit Operating System)

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VBExpress\10.0

RibbonGenerator Details: Windows Ribbon for WinForms

Let take a look into the details of RibbonGenerator of Windows Ribbon for WinForms

What is RibbonGenerator doing?
It generates .ribbon files that contains the definition of the commands and controls and can be used to display a Ribbon control on a Windows Form.

What type of application is RibbonGenerator?
There two applications available that can be used to generate .ribbon files.

  • A custom tool that can be used directly inside Visual Studio and
  • A console application that can be used from command line

Both application are having the same functionality.

How is RibbonGenerator generating .ribbon files?
Because there is no API available, the tool is “just” executing a .bat file. It is a custom .bat file. The content is created by the tool. After the first invocation the “template.bat” is placed in the following folder:
C:\Users\<username>\AppData\Local\RibbonGenerator
You can customize it. When you delete it, it will be recreated after the next invocation of RibbonGenerator.

What does this .bat file doing?
Let take a look into the template.bat file.

"C:\Program Files\Microsoft SDKs\Windows\v7.0\bin\UICC.exe" "{XmlFilename}" "{BmlFilename}" /res:"{RcFilename}"
"C:\Program Files\Microsoft SDKs\Windows\v7.0\bin\rc.exe" /v "{RcFilename}"
cmd /c "("%VS100COMNTOOLS%..\..\VC\bin\vcvars32.bat") && ("%VS100COMNTOOLS%..\..\VC\bin\link.exe" /VERBOSE /NOENTRY /DLL /OUT:"{DllFilename}" "{ResFilename}")"
  1. UICC.exe (available in Windows 7 SDK) is executed to generate a resource definition script file of the Ribbon XAML
  2. rc.exe (available in Windows 7 SDK) is executed to generate a .res file – a windows resource file
  3. link.exe (available in Visual Studio 2010 or Visual C++ 2010 Express)) is executed to generate an unmanaged .dll file. This file is the .ribbon file that can be used by Ribbon control!

The placeholder in there beginning with an “{” are all replaced by the tool. All files that are created will be deleted at the end of the generation process. Only the .ribbon file ({ResFilename} will not be deleted.

Workaround: The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

 

I started using the ADO .NET Entity Framework in a new ASP .NET Project. I´m always creating my applications in a multi-layered architecture. The Attach/Detach functionality of EF is the instrument to go here. But soon i got a curious exception while adding a newly created entity that does not exists in the database before.

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

adress_country

What has happened?
Look at this simple EF diagram. We have a Address and a Country entity type. Country contains all countries in the world. Address can be added, modified, and deleted, while Country only will be referenced.

  • Newly created Address
  • Address references existing Country

 

In a multi-layered environment the method to save the address could look like…

public void AddAdress(Address address)

{

    // Add business rules and validation here…

 

    // Everything is fine, so save the changes now

    using (openorgaEntities context = new openorgaEntities())

    {

        context.AddToAddress(address);

        context.SaveChanges();

    }

}

But the AddAddress method will fail and shows the error message as already shown above.

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

 

After googling the web for more information i found the following post in Microsoft forum.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3945102&SiteID=1

 

The solutions provided in the post are working, but i feel they are all either bad or only working in specific cases.

 

 

The workaround solution:

I wrote an RelationshipManager based helper utility class, that is doing a workaround. Lets take a look to the following illustration.

 

entityhelperutil_flow

The workaround is

  • to remove all relationships first and remember them,
  • then i add or attach the entity object
  • and in the end i attach every existing related entity object and recreate all relationships.

 

Note: I assume here that an Entity already having an EntityKey is an existing entity, while all others are newly created entity objects. This may not be true, in all entity models. So please, check if this is the case also in your model!

 

Implementation of EntityHelperUtil

 

entityhelperutil_cd

The class is having 3 public methods:

  • AddObject replaces the ObjectContext.AddObject method
  • Attach replaces the ObjectContext.Attach method
  • AddObjectOrAttach is combination of both

Additionally the EntityHelperUtil has two methods doing the main work.

  • AddRelationships – Attaches and adds the entity object and every related objects.
  • RemoveRelationships – Removes every relation between the entity objects.

Source of EntityHelperUtil.cs

using System;

using System.ComponentModel;

using System.Collections;

using System.Collections.Generic;

using System.Linq;

using System.Data;

using System.Data.Objects;

using System.Data.Objects.DataClasses;

using System.Text;

using System.Reflection;

 

namespace openorga

{

    /// <summary>

    /// Attach helper for entity framework

    /// </summary>

    public static class EntityHelperUtil

    {

        /// <summary>

        /// Adds or attaches an entity object to the context

        /// </summary>

        public static void AddObjectOrAttach(ObjectContext context, string entitySetName, EntityObject entity)

        {

            if (entity.EntityKey == null)

            {

                AddObject(context, entitySetName, entity);

            }

            else

            {

                Attach(context, entity);

            }

        }

 

        /// <summary>

        /// Adds an entity object to the context

        /// </summary>

        public static void AddObject(ObjectContext context, string entitySetName, IEntityWithRelationships entity)

        {           

            // remove all relations

            List<RelationsShipMapping> map = new List<RelationsShipMapping>();

            RemoveRelationships(map, entity, -1);

 

            // add the entity

            context.AddObject(entitySetName, entity);

 

            // recreate all relationships

            AddRelationships(context, map);

        }

 

        /// <summary>

        /// Attaches an entity object to the context

        /// </summary>

        public static void Attach(ObjectContext context, EntityObject entity)

        {

            // remove all relations

            List<RelationsShipMapping> map = new List<RelationsShipMapping>();

            RemoveRelationships(map, entity, -1);

 

            // attach the entity

            context.Attach(entity);

 

            // recreate all relationships

            AddRelationships(context, map);

        }

 

        /// <summary>

        /// Attaches all related entity object and recreates all relationships

        /// </summary>

        static void AddRelationships(ObjectContext context, List<RelationsShipMapping> map)

        {

            int layer = -1;

 

            // loop through all layers

            do

            {

                layer++;

 

                // return all entity object from a specific layer

                IEnumerable<RelationsShipMapping> rs = from r in map

                                                      where r.Layer == layer

                                                      select r;

 

                // check if there are no further related entity objects

                if (rs.Count<RelationsShipMapping>() == 0)

                    break;

 

                // move through the remembered mappings of the current layer

                foreach (RelationsShipMapping mapping in rs)

                {

                    // check if we need to attach the related entity object

                    IEntityWithKey entityWithKey = mapping.TargetEntity as IEntityWithKey;

                    if (entityWithKey != null && entityWithKey.EntityKey != null)

                        context.Attach(entityWithKey);

 

                    // check if it´s a EntityCollection or EntityReference relationship

                    if (!mapping.IsReference)

                    {

                        // add the related collection entity object

                        MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedCollection").MakeGenericMethod(mapping.TargetEntity.GetType());

                        object col = mi.Invoke(

                            mapping.Entity.RelationshipManager,

                            new object[]{ mapping.RelationshipName, mapping.TargetRole });

                        MethodInfo miAdd = col.GetType().GetMethod("Add");

                        miAdd.Invoke(col, new object[] { mapping.TargetEntity });

 

                    }

                    else

                    {

                        // set the related reference entity object

                        MethodInfo mi = typeof(RelationshipManager).GetMethod("GetRelatedReference").MakeGenericMethod(mapping.TargetEntity.GetType());

 

                        EntityReference er = (EntityReference)mi.Invoke(

                            mapping.Entity.RelationshipManager,

                            new object[]{ mapping.RelationshipName, mapping.TargetRole });

 

                        er.GetType().GetProperty("Value").SetValue(er, mapping.TargetEntity, null);

                    }

                }

            }

            while (true);

 

        }

 

        /// <summary>

        /// Removes all related entity objects

        /// </summary>

        static void RemoveRelationships(List<RelationsShipMapping> map, IEntityWithRelationships entity, int layer)

        {

            // the layer represents the stack in the relationships

            // for example: the related entities to main entity object is layer 0,

            // and the related entites to the related entites is layer 1, etc…

            layer++;

 

            // get a collection of all related conceptual entities.

            IEnumerable<IRelatedEnd> en = entity.RelationshipManager.GetAllRelatedEnds();

 

            foreach (IRelatedEnd end in en)

            {

                // check if the relation is an EntityCollection

                IEnumerable col = end as IEnumerable;

                if (col != null)

                {

                    string colRelationshipName = (string)col.GetType().GetProperty("RelationshipName").GetValue(col, null);

                    string colSourceRoleName = (string)col.GetType().GetProperty("SourceRoleName").GetValue(col, null);

                    string colTargetRoleName = (string)col.GetType().GetProperty("TargetRoleName").GetValue(col, null);

 

                    IEnumerator enu = col.GetEnumerator();

 

                    List<IEntityWithRelationships> mem = new List<IEntityWithRelationships>();

                    while (enu.MoveNext())

                    {

                        IEntityWithRelationships item = (IEntityWithRelationships)enu.Current;

 

                        // check if the relationship is already added the map

                        int alreadyExists = (from c in map

                                            where c.RelationshipName == colRelationshipName && c.TargetRole == colSourceRoleName

                                            select c).Count();

                        if (alreadyExists > 0)

                            continue;

 

                        // remember the relationships and add it to the map

                        RelationsShipMapping mapping = new RelationsShipMapping();

                        mapping.Layer = layer;

                        mapping.RelationshipName = colRelationshipName;

                        mapping.TargetRole = colTargetRoleName;

                        mapping.Entity = entity;

                        mapping.TargetEntity = item;

                        mapping.IsReference = false;

 

                        map.Add(mapping);

 

                        mem.Add(item);

 

                        RemoveRelationships(map, item, layer);

                    }

 

                    // remove the entity collection relationships

                    foreach(IEntityWithRelationships item in mem)

                    {

                        end.Remove(item);

                    }

                }

 

                // check if the relation is an EntityReference

                EntityReference er = end as EntityReference;

                if (er != null)

                {

                    System.Reflection.PropertyInfo pValue = er.GetType().GetProperty("Value");

                    IEntityWithRelationships value = (IEntityWithRelationships)pValue.GetValue(er, null);

                    if (value == null)

                        continue;

 

                    if (er != null)

                    {

                        // check if the reference is already added the map

                        int alreadyExists = (from c in map

                                            where c.RelationshipName == er.RelationshipName && c.TargetRole == er.SourceRoleName

                                            select c).Count();

                        if (alreadyExists > 0)

                            continue;

 

                        // remember the relationships and add it to the map

                        RelationsShipMapping mapping = new RelationsShipMapping();

                        mapping.Layer = layer;

                        mapping.RelationshipName = er.RelationshipName;

                        mapping.TargetRole = er.TargetRoleName;

                        mapping.Entity = entity;

                        mapping.TargetEntity = value;

                        mapping.IsReference = true;

 

                        map.Add(mapping);

 

                        // remove the entity reference relationships

                        pValue.SetValue(er, null, null);

 

                        RemoveRelationships(map, value, layer);

                    }

                }

            }

        }

 

        /// <summary>

        /// Used to remember relationships in the entity object

        /// </summary>

        class RelationsShipMapping

        {

            public int Layer;

            public string RelationshipName;

            public string TargetRole;

            public IEntityWithRelationships Entity;

            public IEntityWithRelationships TargetEntity;

            public bool IsReference;

        }

    }

}

Follow

Get every new post delivered to your Inbox.