2

When I try to set Worksheet's Name property using this test application, it doesn't work due to an 0x80028018 error.

Test form

When I change the value in the first text box, and I click the tab button, nothing happens, while I expect the Sheet1 to be renamed. In the Visual Studio Output Window I can see the following error:

System.Windows.Data Error: 8 : Cannot save value from target back to source. 
BindingExpression:Path=Worksheet.Name; DataItem='ViewModel' (HashCode=35209123); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
ExternalException:'System.Runtime.InteropServices.ExternalException (0x80028018): Exception of type 'System.Runtime.InteropServices.ExternalException' was thrown.
at System.Windows.Forms.ComponentModel.Com2Interop.Com2PropertyDescriptor.SetValue(Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'

The application is a very basic WPF application with a ViewModel class that contains the reference to the Workbook. Then we have a view (xaml) class that binds to the ViewModel.

Viewmodel class:
public class ViewModel
{

    public Worksheet Worksheet { get; set; }
    public ViewModel(Worksheet worksheet)
    {
        Worksheet = worksheet;
    }

    public string Name
    {
        get => Worksheet.Name;
        set => Worksheet.Name = value;
    }
}

Then, in the view, I have a TextBox control that is bound to the Name property of the Worksheet.
Basically there are two ways of doing this:

<TextBox Text="{Binding Worksheet.Name}"/>

or:

<TextBox Text="{Binding Name}"/>

that's because my ViewModel exposes both Worksheet and Name.
And this is where things get interesting.
If I use the first way, it doesn't work and in the debug output of visual studio I find the error shown above.
If I use the second way it works just fine. This way is through Name property, that in turn means that I have an explicit Worksheet.Name = ... in my code (in the Name's setter).
This is the only difference that I see between the two solutions.

Further analysis & questions

The 0x80028018 seems to be a well-known one; there are several articles talking about it. I read this one:
HowTo: Fix “Old format or invalid type library” error (0x80028018)

but:

  1. I can't explain why in my application I see two different behaviors
  2. I can't figure out how to fix the problem at application level so that the binding works as expected

Even if the problem is not so critical and there is an easy workaround for it, there is the risk that it could indicate greater issues. That's why I'm looking for a robust solution.

Left over code, if you want to reproduce it

Beyond the ViewModel class there is the very easy xaml view, (only note: implemented as a UserControl because it has to be loaded into an ElementHost). There is an exceeding TextBox just for allowing the first to lose focus and trigger the update.

<UserControl x:Class="ExcelAddIn12.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:ExcelAddIn12"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
    <StackPanel>
        <!--the non-working solution: -->
        <TextBox Text="{Binding Worksheet.Name}"/>
        <TextBox/>
    </StackPanel>
</UserControl>

It remains only the launch & wiring code. As a launcher i used a Ribbon (designed, not xaml) with a button.

public partial class Ribbon1
{
    private void Ribbon1_Load(object sender, RibbonUIEventArgs e) { }

    private void button1_Click(object sender, RibbonControlEventArgs e)
    {

        Worksheet myWorksheet = (Worksheet)Globals.ThisAddIn.Application.ActiveSheet;

        ViewModel vm = new ViewModel(myWorksheet);

        UserControl1 view = new UserControl1() { DataContext = vm };

        Form form = new Form();

        ElementHost wpfHost = new ElementHost() { Dock = DockStyle.Fill, Child = view };
        form.Controls.Add(wpfHost);

        form.Show();
    }
}
Versions:
  • Visual Studio 2017 (EN)
  • Excel 2010 (EN)
AgostinoX
  • 7,477
  • 20
  • 77
  • 137
  • Did you try a dollar sign in front of the worksheet name ($Worksheet.Name). The dollar sign in excel indicates you have a System Table which is used internally in excel for worksheet. You can also try $Sheet.Name instead which sometimes works. – jdweng Jul 01 '20 at 12:13
  • This is a C# application, where should I add the $ sign? – AgostinoX Jul 01 '20 at 12:16
  • The dollar sign is part of the name. So maybe : "$" + Worksheet.Name – jdweng Jul 01 '20 at 12:22
  • The line Worksheet.Name already works. I tried adding the $ to the name in the textbox, with no luck. – AgostinoX Jul 01 '20 at 12:26
  • Are there space in the sheet name. You may need to put square brackets if the are spaces. – jdweng Jul 01 '20 at 12:37
  • No there aren't. I tried with the square brackets the same, it didn't work. – AgostinoX Jul 01 '20 at 12:38
  • My best guess is: `Worksheet` member of ViewModel wasn't properly set. When it's a `null`, then such of message is generated. Debug the program, to find out. On the other hand, the name may already exists... – Maciej Los Jul 09 '20 at 13:38
  • @Maciej Los, this seems to have nothing to do with null – AgostinoX Jul 09 '20 at 14:02
  • I'd like to be wrong... Can you confirm that Worksheet is not `null`? Behind this error message is primal error: `The instance of an object is not set...`. You can't set the name of worksheet, because it's a `null`! – Maciej Los Jul 09 '20 at 15:50
  • Looks like you are trying to bind to a property of a COM object. See [Binding to a COM Object - Cannot resolve a Property](https://stackoverflow.com/questions/29163298/binding-to-a-com-object-cannot-resolve-a-property) for more information (the last answer contains a couple of links that might be of interest to you). Basically, using a wrapper property is the way to go here (and one of the main responsibilities of a ViewModel anyway). – lauxjpn Jul 10 '20 at 11:25
  • You might also want to compare the `System.Threading.Thread.CurrentThread.CultureInfo` in your own (working) property setter and when the exception is thrown. The article you linked and this [question](https://stackoverflow.com/questions/5180713/old-format-or-invalid-type-library-exception-from-hresult-0x80028018-type-e) on stack overflow state that it is a current culture divergence. – lauxjpn Jul 10 '20 at 11:45

1 Answers1

1

The article you linked states the following:

Most of the Excel Object Model methods and properties require specifying an LCID (locale identifier). If a client computer has the English version of Excel, and the locale for the current user is configured for another language (e.g. German or French), Excel may fire the “Old format or invalid type library” exception with error code 0x80028018 (-2147647512).

It also tells you to set the System.Threading.Thread.CurrentThread.CultureInfo to the one Excel uses. The simplest way to do this is to do it once globally for the current (UI) thread:

Thread.CurrentThread.CurrentCulture = new CultureInfo(Application.LanguageSettings.LanguageID[MsoAppLanguageID.msoLanguageIDUI])

As for the reason this is happening, it looks like a different LCID is used when calling directly through binding and when calling from your wrapper property. You might want to check your Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture properties when those calls are made.


Here are a couple of links for a bit more background information about the different culture settings:

In short, CurrentThread.CurrentCulture is your current region setting (that you can change via control panel), CurrentThread.CurrentUICulture corresponds to the language of Windows that you installed (that you usually cannot change easily), CultureInfo.DefaultThreadCurrentCulture will change the current CurrentThread.CurrentCulture property and setup the default value for future threads and finally there is something like the following line, that applies to WPF bindings:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        XmlLanguage.GetLanguage(
            CultureInfo.CurrentCulture.IetfLanguageTag)));

Of course you could also just change your local users culture to the one Excel is using (Englisch).

lauxjpn
  • 4,749
  • 1
  • 20
  • 40
  • Ok It works if you put the line you said in the ThisAddIn_Startup event. Before doing that i checked that in the Worksheet getter Thread.CurrentThread.CurrentUICulture returns {en-US} while Thread.CurrentThread.CurrentCulture returns {it-IT}. This is strange, seems like wpf uses non-UI culture while the non-UI code uses the UI setting. Seems quite paradoxical. – AgostinoX Jul 11 '20 at 09:27
  • I added some more background information, to shine more light upon the different levels of culture settings being involved here. – lauxjpn Jul 11 '20 at 23:40