Excel/VSTO: error 0x80028018 only when binding directly to interop.-provided property

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)
c#
excel
wpf
vsto
asked on Stack Overflow Jul 1, 2020 by AgostinoX • edited Jul 1, 2020 by AgostinoX

1 Answer

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).

answered on Stack Overflow Jul 10, 2020 by lauxjpn • edited Jul 11, 2020 by lauxjpn

User contributions licensed under CC BY-SA 3.0