0

The issue I'm running into here is that I am building a combined COM and Automation Add In for Excel. When trying to access Properties.Settings.Default in the Automation Add In class I have created, the values coming through are the ones I have set as defaults for the project even though the COM Add In returns values that have been saved via a configuration form.

All classes are in the same project and under the same namespace and the class files for my Configuration form, COM add-in and Ribbon are all able to access the updated Properties.Settings.Default values. My Automation add-in and a static class (that handles all the heavy lifting) are not able to access the updated values.

COM add-in:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Excel;
using System.Windows.Forms;

namespace DBLink
{
    public partial class ThisAddIn
    {
        //public Dictionary<string, string> reports;
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            MessageBox.Show("User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ");
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }
}

Ribbon:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Office.Tools.Ribbon;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Tools.Excel;

namespace DBLink
{
    public partial class Ribbon1
    {
        private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
        {
            //Even setting the connection string from a class that has
            //access to the correct values fails and says the connection
            //string is not initialised.
            string conString;
            conString = "User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ";
            DataLink.connString = conString;

            MessageBox.Show("User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ");
        }

        private void button1_Click(object sender, RibbonControlEventArgs e)
        {
            Form frm = new Form1();
            frm.ShowDialog();
        }

        private void button2_Click(object sender, RibbonControlEventArgs e)
        {
            try
            {
                DataLink.LoadData(Globals.ThisAddIn.Application.ActiveWorkbook);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
        }

        private void button5_Click(object sender, RibbonControlEventArgs e)
        {
            //Do Stuff
        }

        private void button3_Click(object sender, RibbonControlEventArgs e)
        {
            //Do Stuff
        }
    }
}

Form:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows.Forms;

namespace DBLink
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Properties.Settings.Default.DBServer = textBox1.Text;
            Properties.Settings.Default.DBName = textBox2.Text;
            Properties.Settings.Default.DBUser = textBox3.Text;
            Properties.Settings.Default.DBPassword = textBox4.Text;
            Properties.Settings.Default.Save();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            SqlConnection con = new SqlConnection("User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ");
            try
            {
                con.Open();
                MessageBox.Show("Connected successfully");
                con.Close();
            }
            catch
            {
                MessageBox.Show("There was a problem connecting to the database. Please confirm the details!");
            }
        }
    }
}

Automation:

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace DBLink
{
    [Guid("19C7ACDB-1572-4988-984F-3C56AEF117A5")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]

    public partial class MyFunctions2
    {
        string connectionString;
        public MyFunctions2()
        {
        }

        private static string GetSubKeyName(Type type, string subKeyName)
        {
            System.Text.StringBuilder s = new System.Text.StringBuilder();
            s.Append(@"CLSID\{");
            s.Append(type.GUID.ToString().ToUpper());
            s.Append(@"}\");
            s.Append(subKeyName);
            return s.ToString();
        }
        public string MTD(int year, int period, string costCentre, string account)
        {
            connectionString = "User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ";
        return DataLink.MTD(year, period, costCentre, account, connectionString).ToString();
        }

        public double MultiplyNTimes2(double number1, double number2, double timesToMultiply)
        {
            return DataLink.MultiplyNTimes(number1, number2, timesToMultiply);
        }

        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type type)
        {
            Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"));
            RegistryKey key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true);

            key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll", RegistryValueKind.String);
        }

        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type type)
        {
            Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false);
        }
    }
}

Finally, the static class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Office.Tools.Ribbon;
using Excel = Microsoft.Office.Interop.Excel;

namespace DBLink
{
    public static partial class DataLink
    {
        private static string commandText = "select case when ACClass in (2,3,4,5) then SUM(ISNULL(POCredit,0)-ISNULL(PODebit,0)) else SUM(ISNULL(PODebit,0)-ISNULL(POCredit,0)) end as value from Account join Post on ACID = POAccountID left join Segment on SID = POSegmentID join Journal on POJournalID = JID where DatePart(month,JDate) = @month and DatePart(year,JDate) = @year and ACCode = @account and (SName = @seg or @seg = '*') group by ACClass";
        public static string connString="";
        private static SqlConnection con;

        public static void SetConnString(string connection)
        {
            connString = connection;
        }
        public static void LoadData(Excel.Workbook wb)
        {
            //Do stuff... may be deprecated before release.
        }

        public static double MTD(int year, int period, string costCentre, string account, string conString)
        {
            DataTable dt = new DataTable();
            DataSet ds = new DataSet();
            double result = 0;
            int month = period > 6 ? period - 6 : period + 6;
            int actualYear = month > 6 ? year - 1 : year;

            MessageBox.Show("User ID = " + Properties.Settings.Default.DBUser + "; Password = " + Properties.Settings.Default.DBPassword + "; Data Source = " + Properties.Settings.Default.DBServer + "; Initial Catalog = " + Properties.Settings.Default.DBName + "; ");

            con = new SqlConnection(connString);
            try
            {
                using (SqlCommand cmd = new SqlCommand(commandText, con))
                {
                    cmd.Parameters.AddWithValue("@month", month);
                    cmd.Parameters.AddWithValue("@year", actualYear);
                    cmd.Parameters.AddWithValue("@account", account);
                    cmd.Parameters.AddWithValue("@seg", costCentre);
                    MessageBox.Show(cmd.CommandText);
                    MessageBox.Show(cmd.Connection.ConnectionString);
                    cmd.Connection.Open();
                    dt.Load(cmd.ExecuteReader());
                    if (dt.Rows.Count > 0)
                    {
                        MessageBox.Show("We have a row");
                        ds.Tables.Add(dt);
                        result = double.Parse(ds.Tables[0].Rows[0].ItemArray[0].ToString());
                    }
                    cmd.Connection.Close();
                    MessageBox.Show(result.ToString());
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }

            return result;
        }
    }
}

All I can think of is that somehow 2 or more instances of the settings are being created. If anyone has any ideas on how this might be resolved, that would be excellent.

EDIT: Playing about a bit more, it seems the COM Add-in only sees the saved values when loading and then sees the original. The Ribbon can see the settings the whole time.

EDIT 2: Have worked it out and put the answer in for future reference, will mark as answer once the 2 day limitation is up.

Aaron
  • 167
  • 1
  • 10
  • Are the settings in **User** scope? and is the config file beside the assembly? – Lei Yang Mar 02 '17 at 01:17
  • @LeiYang yes, the settings are in the User scope (ran into that issue ages ago in another project). The DBLink.dll.config.deploy file is sitting alongside the DBLink.dll.deploy file in the version controlled Application Files folder that Visual Studio generates when you use the publish option. If I use Build, I get a DBLink.dll.config file sitting alongside DBLink.dll. – Aaron Mar 02 '17 at 01:44

1 Answers1

0

Okay, so I found a solution as everything was loading into different AppDomains.

Step 1: Call this.Application.Evaluate("=MTD()"); in the ThisAddIn_Startup method. This meant both my COM and Automation add-in were running under the same AppDomain.

Step 2: Pass the connection string to my DataLink class in the ThisAddIn_Startup method.

A huge amount of Googling to find that: https://groups.google.com/forum/#!msg/microsoft.public.office.developer.com.add_ins/_ZP9udZJ7bI/Q9fU6rsGhTcJ

Aaron
  • 167
  • 1
  • 10