Posts mit dem Label C# werden angezeigt. Alle Posts anzeigen
Posts mit dem Label C# werden angezeigt. Alle Posts anzeigen

Dienstag, 26. Februar 2013

E_ACCESSDENIED with Office Interop

Hi,
once upon a time, I had a nice little function which would open an Word-Document via Microsoft-Interop, export all bookmarks as a Dictionary<BookmarkName, StringBookmarkValue>, I would modify this dictionary and by pass it on to another little function, which would afterwards set all the values of the bookmarks. This worked very well until .... One day the server was upgraded to Windows 2012 and Office 2010 and since than nothing seemed to work any more.

After getting the following error:
System.UnauthorizedAccessException: Retrieving the COM class factory for component with CLSID {000209FF-0000-0000-C000-000000000046} failed due to the following error: 80070005 Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)).
   at System.Runtime.Remoting.RemotingServices.AllocateUninitializedObject(RuntimeType objectType)
   at System.Runtime.Remoting.Activation.ActivationServices.CreateInstance(RuntimeType serverType)
   at System.Runtime.Remoting.Activation.ActivationServices.IsCurrentContextOK(RuntimeType serverType, Object[] props, Boolean bNewObj)
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)

...and spending roughly an our on the Internet I found following link: http://support.microsoft.com/kb/257757/en-us

Since non of the mentioned solutions worked for me in Word 2012 I decided to let Interop be and switch to OpenXML.... here you can read how to do the same function to replace bookmarks in OpenXML with the OpenXML SDK

Handling Bookmarks in OpenXML Word-Documents

Hi,

I needed to reimplement my function of exporting all bookmarks of a word-document into a dictionary and than setting them based on the changes in the dictionary from Word-Interop to OpenXML SDK.

I've found a very helpful answer on stackoverflow suggesting the following solution:

IDictionary<string, BookmarkStart> bookmarkMap = new Dictionary<string, BookmarkStart>();

foreach (BookmarkStart bookmarkStart in file.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
{
    bookmarkMap[bookmarkStart.Name] = bookmarkStart;
}

foreach (BookmarkStart bookmarkStart in bookmarkMap.Values)
{
    Run bookmarkText = bookmarkStart.NextSibling<Run>();
    if (bookmarkText != null)
    {
        bookmarkText.GetFirstChild<Text>().Text = "blah";
    }
}

Seems very easy, right? It was a great starting point however only a starting point.

First thing I discovered...


There are HIDDEN bookmarks. This was weird in the beginning however you can see quickly the pattern, all of them start with an _ (underscore) and after finding this trustworthy page as the first answer from Google, to confirm if my assumption was correct, I didn't bother looking further, so just add

bookmarkStart.Name.StartsWith("_")

and the problem is solved.

Next Problem occurred...


You can define bookmarks for CELLs and than they behave totally different. So how do they behave? 
They are all stuck in the first cell of each row.
So how do I know to which cell they belong? Word seems to know it.
BookmarkStart has a property ColumnFirst. Normally the value is NULL, however in this case it has the 0-based index of the column it refers to. If your bookmarks stretch over multiple cells, there is also a ColumnLast (for my case ColumnFirst == ColumnLast).
However retrieving the data now is a bit tougher, so let's take a step back. First I created some Extension-Methods to  make my functions smaller:

public static T GetFirstDescendant<T>(this OpenXmlElement parent) where T : OpenXmlElement
{
    var descendants = parent.Descendants<T>();

    if (descendants != null)
        return descendants.FirstOrDefault();
    else
        return null;
}

public static T GetParent<T>(this OpenXmlElement child) where T : OpenXmlElement
{
    while (child != null)
    {
        child = child.Parent;

        if (child is T)
            return (T)child;
    }

    return null;
}

Now having those helpful methods let's start solving the actual problem.
First we need to add another condition.

if (bookmarkStart.ColumnFirst != null)
    return FindTextInColumn(bookmarkStart);

And actually implement FindTextInColumn

private Text FindTextInColumn(BookmarkStart bookmark)
{
    var cell = bookmark.GetParent<TableRow>().GetFirstChild<TableCell>();

    for (int i = 0; i < bookmark.ColumnFirst; i++)
    {
        cell = cell.NextSibling<TableCell>();
    }

    return cell.GetFirstDescendant<Text>();
}

As you can see, I'm looking for the Parent of type TableRow and take the first TableCell-child of this row. Afterwards I take the NextSibling of type TableCell until I reach the necessary column. Than I just need to return the first Text which can be found in this column. I myself don't really care how many texts exist, since I need only one to replace the content and keep the formatting. Later you will see that I delete additional Text-elements.

So, problem solved one more time. What else could there be?

While it's not a problem to read bookmark-values, it is one, when you are trying to set them:

Bookmarks can be empty - not having any element...


However once you figured out that the bookmark really is empty it is quite easy and straight forward to add a simple Run with a Text after the BookmarkStart, the following function takes care of this very easily:

private void InsertBookmarkText(BookmarkStart bookmark, string value)
{
    bookmark.Parent.InsertAfter(new Run(new Text(value)), bookmark);
}

This is solved very easily, however as I suggested, the problem is not to insert the value, but to figure out if it needs to be inserted. For this I present you the last problem I've found and solved for retrieving the values from bookmarks:

How to find Text and Run if they are not siblings of the bookmark as suggested by the initial solution?


For this, I expanded the simple search for Run from the initial solution into something more sophisticated. I don't know the specification of OpenXML-Documents so it might be unnecessary, but it provides also the information if the bookmark as such is empty.

First, here are 2 new helping Extension-Methods, I will use later on:

public static bool IsEndBookmark(this OpenXmlElement element, BookmarkStart startBookmark)
{
    return IsEndBookmark(element as BookmarkEnd, startBookmark);
}

public static bool IsEndBookmark(this BookmarkEnd endBookmark, BookmarkStart startBookmark)
{
    if (endBookmark == null)
        return false;

    return endBookmark.Id == startBookmark.Id;
}

And now the little magic...

var run = bookmarkStart.NextSibling<Run>();

if (run != null)
    // I've found a run and suppose it has a Text
    return run.GetFirstChild<Text>(); 
else
{
    // I will go through all the siblings and try to find any Text
    Text text = null;
    var nextSibling = bookmarkStart.NextSibling();
    while (text == null && nextSibling != null)
    {
        if (nextSibling.IsEndBookmark(bookmarkStart))
            // I've reached the end of the bookmark and couldn't find any Text
            return null;

        text = nextSibling.GetFirstDescendant<Text>();
        nextSibling = nextSibling.NextSibling();
    }

    return text;
}

Having this defined I managed to retrieve and replace correctly all bookmarks. We just forgot to solve the last issue - removing unnecessary Text-elements. In the following function, I want to remove all Text-elements within my bookmark except the parameter keep:

private void RemoveOtherTexts(BookmarkStart bookmark, Text keep)
{
    if (bookmark.ColumnFirst != null) return;

    Text text = null;
    var nextSibling = bookmark.NextSibling();
    while (text == null && nextSibling != null)
    {
        if (nextSibling.IsEndBookmark(bookmark))
            break;

        foreach (var item in nextSibling.Descendants<Text>())
        {
            if (item != keep)
                item.Remove();
        }
        nextSibling = nextSibling.NextSibling();
    }
}

Now all my problems are SOLVED :)
Hope it could help you as well, here you can find the whole code with all the functions, I used:

Montag, 7. Januar 2013

Problem Adding Ribbon-Control to Office AddIn Projects

ThisRibbonCollection' does not contain a definition for 'GetRibbon'....

If you see this error, don't panic, the solution is rather simple:
To make matters easier for you, Office AddIns have a partial class "ThisRibbonCollection" which is automatically used to define your Ribbon Control as the MainRibbonControl of the application. This might seem, after the creation and a first run rather as magic, unless of course, you want to have all your application controls in a separate folder and therefore separate namespace from ThisAddIn.cs. (thx for the hint to http://qa.social.msdn.microsoft.com/Forums/en-SG/vsto/thread/3b117e7a-e3d5-4f10-8262-358b04494230)

However there is also a solution to this, allowing you to keep your RibbonControl in a separated folder and namespace:
  1. Open <RibbonControl>Designer.cs
  2. Go to the end of the file where you see partial class ThisRibbonCollection
  3. Create at the end of the file a new namespace section, with the root-namespace of your AddIn
  4. Move the class into the new namespace
  5. Since you are in a different namespace now, don't forget to add the necessary usings
Happy Coding...

Samstag, 1. September 2012

How To Use Generalisation With Generics

Hi, did you also have problems using generics with too specific parameters?

Example:
public abstract class Person 
{ 
  public void DoSomething(IEnumerable<Person> persons) { }
}

public class Teacher : Person { }
public class Student : Person { }

The question - why won't it work?

Exactly, if I want to call this method with IEnumerable<Student> I'd need to cast it. And not just normally cast, but with the extension method .Cast<Person>()
There are several more or less valid reasons for this need, which doesn't change the fact that it's annoying and doesn't serve the purpose of what I want to allow.

A very easy workaround which allows us to do everything we need:

public abstract class Person 
{ 
  public void DoSomething<T>(IEnumerable<T> persons) where T : Person { }
}

public class Teacher : Person { }
public class Student : Person { }

Ok, but what if I want my DoSomething of Teacher only be used with Teacher?

public abstract class Person<T> where T : Person 
{ 
  public void DoSomething(IEnumerable<T> persons) { }
}

public class Teacher : Person<Teacher> { }
public class Student : Person<Person> { }

And this was only the starter - what if you want to have a List of a List, but don't really care if it's an array, IEnumerable, Collection or an actual List?

public abstract class Person 
{ 
  // Limits you to IEnumerable<Person> as inner generic.
  // Example IEnumerable<Person>[] or List<IEnumerable<Person>> ...
  public void DoSomethingMore(IEnumerable<IEnumerable<Person>>) { }

  // No limitations
  public void DoSomethingBetter<T>(IEnumerable<T> persons) 
      where T : IEnumerable<Person> { }
}

public class Teacher : Person<Teacher> { }
public class Student : Person<Person> { }

As you can see you can call DoSomethingBetter any way of combining collections as you want. Even with Person[][] or List<HashSet<Person>>.

Happy Coding!

Peter

Sonntag, 19. August 2012

Namespace Problems in C#

Hallo, just stumbled over two problems with namespaces in C#

First things first:

The type name 'IO' does not exist in the type 'Microsoft.Office.Interop.Word.System' when you have the following usings defined:
    using System.IO;
    using Microsoft.Office.Interop.Word;
The problem is that there exists a namespace Microsoft.Office.Interop.Word.System and Visual Studio or MSBuild somehow apply the System.*-namespaces (even they're defined before) to Micorosft.Office.Interop.Word and make it Microsoft.Office.Interop.Word.System.*

There is also a StackOverflow question about it, but unfortunately without any answer. My solution to this was more of a workaround, I named the namespace Microsoft.Office.Interop.Word.
    using System.IO;
    using word = Microsoft.Office.Interop.Word;
The downside of this workaround is that you need to use "word" in front of every type from the namespace. But at least the code compiles.

The second problem...

was a bit my fault but also not very clear in the beginning.
I have 2 Projects:
  • Common (for common functions, ExtensionMethods, ... which I may use also in the future)
  • Editor - the actual project
in Editor I had another folder "Common" which lead to the namespace Editor.Common

Now, the 1 Million $ question - how do you use in a class which has the namespace Editor.Common a class from the project and namespace Common? - The answer is simple: you can't.

And the moral of this story - don't mix your namespaces and give them names which are as unique as possible.


Happy Coding!
Peter

Sonntag, 12. August 2012

Simple Zoom for WPF Controls

Hallo again,
recently, I had to implement a zoom – in / out – functionality into a WPF application. After some research I found out that there is a RenderTransform on the Grid-Control. Here you can define a ScaleTransform which is able to scale everything in the grid by a value of type double (so 0.5 makes everything half the size, 2 makes everything double the size). One more great but probably not often needed feature is, that you can define different values for the horizontal and the vertical scale.To get an easy and quick return, just use the following XAML-Code:
<Grid x:Name="LayoutRoot">
  <Grid.RenderTransform>
    <ScaleTransform>
      <ScaleTransform.ScaleX>
        <Binding Path="ScaleFactor" ElementName="Window"/>
      </ScaleTransform.ScaleX>
      <ScaleTransform.ScaleY>
        <Binding Path="ScaleFactor" ElementName="Window"/>
      </ScaleTransform.ScaleY>
    </ScaleTransform>
  </Grid.RenderTransform>

  <Grid.RowDefinitions>
    <RowDefinition Height="66"/>
    <RowDefinition Height="*"/>
    <RowDefinition Height="30"/>
  </Grid.RowDefinitions>

  <!-- enter your controls here -->
</Grid>

As you see there is a binding to ScaleFactor, which is Dependency-Property on the Window element. Now you only have to set the ScaleFactor to change the zoom-factor, this was easy.
When you want to implement this on your main-window, which doesn’t have any scroll-bars you won’t be very. You get the zoom-functionality but when you zoom-in, you won’t see everything anymore, and when you zoom-out, there will be unused space.
I can’t help you with the unused space, because this can only be filled, when you actually have scroll bars, because your control ist too large for it’s container. So the best thing is, just ignore the zoom-out and set the minimum zoom value to 1 (100%)
But for the zoom-in, there is a very simple trick, just add 2 more RowDefinitions. Why 2? Because the RowDefinitions with static values (pixel, auto) change by a slightly other factor then the dynamic ones (star). So now, our definition should look something like this:
<Grid x:Name="LayoutRoot">
  <Grid.RenderTransform>
    <ScaleTransform>
      <ScaleTransform.ScaleX>
        <Binding Path="ScaleFactor" ElementName="Window"/>
      </ScaleTransform.ScaleX>
      <ScaleTransform.ScaleY>
        <Binding Path="ScaleFactor" ElementName="Window"/>
      </ScaleTransform.ScaleY>
    </ScaleTransform>
  </Grid.RenderTransform>

  <Grid.RowDefinitions>
    <RowDefinition Height="66"/>
    <RowDefinition Height="*"/>
    <RowDefinition Height="30"/>
    <!– zooming row definitions –>
    <RowDefinition Height="0"/>
    <RowDefinition Height="0"/>
  </Grid.RowDefinitions>
  
  <!-- enter your controls here -->
</Grid>
As you can see, I added 2 more RowDefinitions with Height=”0″ but this will change, in the code, this is the definition of the ScaleFactor and what happens, when it changes:
public double ScaleFactor
{
  get { return (double)GetValue(ScaleFactorProperty); }
  set { SetValue(ScaleFactorProperty, value); }
}

// Using a DependencyProperty as the backing store for ScaleFactor. 
// This enables animation, styling, binding, etc…
public static readonly DependencyProperty ScaleFactorProperty =
  DependencyProperty.Register("ScaleFactor", typeof(double), 
    typeof(MainWindow), new FrameworkPropertyMetadata(1.0,
      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
      ScaleFactorPropertyChangedCallback));

private static void ScaleFactorPropertyChangedCallback(
  DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  MainWindow me = d as MainWindow;

  if (me != null)
  {
    double starSize = 0, staticSize = 0;

    for (int i = 0; i < me.LayoutRoot.RowDefinitions.Count – 2; i++)
    {
      if (me.LayoutRoot.RowDefinitions[i].Height.IsStar)
        starSize += me.LayoutRoot.RowDefinitions[i].Height.Value;
      else if (me.LayoutRoot.RowDefinitions[i].Height.IsAuto)
        staticSize += me.LayoutRoot.RowDefinitions[i].MinHeight;
      else
        staticSize += me.LayoutRoot.RowDefinitions[i].Height.Value;
    }

    me.LayoutRoot.RowDefinitions
      [me.LayoutRoot.RowDefinitions.Count - 2].Height = 
        new GridLength(staticSize * (me.ScaleFactor – 1), GridUnitType.Pixel);
    me.LayoutRoot.RowDefinitions
      [me.LayoutRoot.RowDefinitions.Count - 1].Height = 
        new GridLength(starSize * (me.ScaleFactor – 1), GridUnitType.Star);
  }
}
Now, you can see, that I dynamically sum up the dynamic and static heights of all rows.
In the end, I set the height of the row before last to “Sum(height of all RowDefinitions with a static size) * (ScaleFactor – 1)” and I do the same with the last row, just for all dynamic RowDefinitions.
But why ScaleFactor – 1 ?
The answer is easy, so I can assure, that everything else my other rows are still fully visible, how?
Because the ScaleFactor changed, everything got bigger, so for example it changed to 1,5 so now everything is 150% of the normal size. But in the window only fits 100% so I can only see 2/3 of my grid. By resizing the 2 rows to the half of the grid (1,5 – 1 = 0,5) the rest still fits into the 100% and the 2 dummy rows are hidden in an unknown space.

Happy Coding!

How To Stop a Storyboard


I have an application with a storyboard, which turns a icon that indicates that the application is currently busy. Because I don’t have any chance to know, how long the application will be busy, the RepeatBehavior on the application is set to Forever and I have to stop it manually.
OK, seams to be easy, the storyboard has everything needed to stop it, a Pause-, Stop- and Remove-method, but somehow, nothing works. After some time I detected one line in the Output-Window of Visual Studio:
System.Windows.Media.Animation Warning: 6 :
Unable to perform action because the specified Storyboard was never applied to
this object for interactive control.; Action='Remove'; ...
After some research the answer was very easy and very Microsoft-like, when you start the storyboard with the Begin-method, there is a second parameter of type Boolean, isControllableThis has to be set to true, afterwards all the methods mentioned above will work.
Happy Coding!