Click here to Skip to main content
Click here to Skip to main content
Go to top

AddIn Enabled Applications

, 7 May 2008
Rate this:
Please Sign up or sign in to vote.
Using the AddIn model pipeline.

Introduction

This article is all about using the .NET 3.5 System.Addin namespace. This article owes much to the fabulous WPF book by Mathew McDonald (who is an uber genius in my opinion).

What this article is attempting to demonstrate is fairly simple actually.

There is an AddIn contract (INumberProcessorContract implementing IContract) that defines what all add-ins should do. There is also another Contract that specifies how the add-in should communicate back to the Host application. This Contract is called IHostObjectContract, which again implements IContract.

There is a pipeline to support the AddIn model, and finally, there is a host application that will host the add-ins. In essence, that is all this article is about.

Before I start diving into the details, it's probably worth mentioning what the add-in(s) provided by the demo application actually do. I have created several add-ins that all provide some functionality that matches the method signature defined within the AddIn INumberProcessorContract contract (implementing IContract). Basically, all the add-ins in the demo app will provide a single method that has the following signature:

List<int> ProcessNumbers(int fromNumber, int toNumber)

I have created three separate add-ns, which do various things to do with numbers:

  • Finobacci numbers add-in: Returns a list of Fibonacci numbers between fromNumber and toNumber.
  • Loop numbers add-in: Returns a list of numbers between fromNumber and toNumber
  • Prime numbers add-in: Returns a list of prime numbers between fromNumber and toNumber

And within the attached demo application, I wanted to show how the add-in could report progress back to the host application, so this is also part of the attached demo code.

We will now go on to discuss how the .NET 3.5 System.Addin namespace allows us to create add-ins (not easily, but it does allow it).

The following subsections will go into this in a little more detail:

What are AddIns

AddIns (sometimes called Plugins) are separately compiled components that an application can locate, load, and make use of at runtime (dynamically). An application that has been designed to use add-ins can be enhanced (by developing more add-ins) without the need for the original application to be modified or recompiled and tested (though the add-ins should be tested). For some time, .NET has allowed developers to create applications that use add-ins, but this more than likely used the System.Reflection namespace, and was probably a fair bit of work. With .NET 3.5 comes a new namespace, namely the System.Addin namespace. The .NET team has created a very flexible framework for building add-ins, this is known as the AddIn Pipeline (System.AddIn.Pipeline). By using the AddIn Pipeline, the necessary plumbing is already in place to allow the AddIn model to work. So that's a good thing. The down side to this is that you must implement at least 7 different components to implement the most basic AddIn model. The next section will discuss the AddIn pipeline concept (though it will not discuss the System.AddIn.Pipeline namespace), and shall discuss what happens in each of the 7 (minimum) components that are required in order to correctly setup an AddIn model enabled application.

Note: The AddIn model is equally at home in WinForms or WPF (and possibly ASP.NET, though I didn't try that). I shall be using WPF as I like it, though the topics discussed here after would be equally suited to working with WinForms.

The AddIn Pipeline

The heart of the Addin model is the pipeline. The pipeline is a chain of components that allows the application to communicate with the add-in. At one end of the pipeline is the Host Application, and at the other end is the add-in, and in between are five other components that facilitate the add-in operation and interaction. Consider the following diagram:

At the center of this figure is the Contract (implements System.AddIn.Contract.IContract), which includes one or more interfaces that define how the host application and the add-ins can interact. The Contract can also include serializable types that are planned to be used between the host application and the add-in. Microsoft designed the AddIn model with extensibility in mind, and as such, both the host application and the actual add-in don't actually use the Contract directly; rather they use their own respective versions of the Contract, called Views. The host application uses the host view, while the add-in uses the AddIn view. The views normally contain an abstract class that reflects the interfaces in the Contract interface (though the View doesn't inherit from the Contract or implement the System.AddIn.Contract.IContract interface).

Although the Views and the Contract have similar method signatures, they are totally separate; it's up to the Adapters to join them together. There are two Adapters: one for the host application which inherits from the abstract host view class, whilst the AddIn adapter inherits from System.AddIn.Pipeline.ContractBase and the actual application specific interface that is defined in the Contract component. One thing to note here is that because the AddIn adapter inherits from System.AddIn.Pipeline.ContractBase, which in turn inherits from MarshalByRefObject (MarshalByRefObject enables access to objects across application domain boundaries in applications that support Remoting), the add-in is allowed to cross application domain boundaries.

The flow is something like this:

  1. The host application calls one of the methods in the host view. But the host view is really an abstract class. What is actually happening is that the host application is really calling a method on the host adapter though the host view. This is possible as the host adapter inherits from the host view.
  2. The host adapter then calls the correct method in the contract interface, which is implemented by the AddIn adapter.
  3. The AddIn adapter calls a method in the AddIn View. This method is implemented by the add-in that actually does the work.

Consider the following figure for a single add-in within the pipeline:

This diagram shows what the pipeline would look like for a single add-in. If we wish to create more add-ins (as the demo application does), we simply need to create more concrete classes that inherit from the abstract AddIn View class.

AddIn Model Folder Structure

The AddIn model relies on a strict directory structure. However, the required directory structure is actually separate from the application, so it's fine to have the projects elsewhere as long as they build to a common folder. For example,s the demo application uses an Output folder under the solution, which must have the following subfolders:

The AddinIns folder above must also have a separate folder for each available add-in, as shown below (using the three add-ins for the demo application):

This folder structure is not optional at all. It must be exactly as shown in the order for the AddIn model to work correctly. This example assumes that the host application is deployed in the Output folder.

Preparing a Solution to Use the AddIn Model

As the AddIn model file structure is mandatory, if you leave out or misname a particular project, you will get a runtime Exception. Here is a step by step Visual Studio guide of how I managed to get the demo application to work.

  1. Create a top level directory to hold all the seperate components. I called mine AddInTest.
  2. Create either a WinForms or WPF based host application. It doesn't matter what it is called, but it must be placed in the top level folder from step 1.
  3. Add a new Class Library project for each pipeline component. You will also need to create a project for at least one add-in component. This should give you something like the following within Visual Studio (note there are actually three add-ins in the solution shown here):
  4. Next, you need to create a build directory in the top level directory (AddInTest\ for the demo app). This is where the AddIn pipeline components will be placed once they are compiled. This folder is called "Output" in the demo application.
  5. As each of the pipeline components is constructed and built, you will need to modify the build path to point to the relevant subdirectory underneath the top level build folder. This is shown in the figure below (which we saw earlier):
  6. The build path can be changed using Visual Studio by right clicking the project and getting up the project settings. See the figure below:

Referencing Pipeline Components

There is one last issue that you have to be aware of when building AddIn enabled applications. There will obviously be a need to reference one or more of the AddIn pipeline components within another pipeline component project. However, as the AddIn model relies on the strict mandatory file structure mentioned above, when adding a reference to another of the pipeline components, care needs to be taken that the referenced DLL is not copied, such that the AddIn model will only ever use the DLL that is found within the related AddIn model file system folder. To make sure this all works as expected, we need to make sure that any referenced pipeline DLL is set with the "Copy Local" property set to "False". This ensures that the correct file system placed DLL is used within the host application. Basically, the host application looks for the various components at certain file system locations. The "Copy Local" property may be changed by clicking on the referenced DLL and examining the value of the "Copy Local" property within the property grid. If it is set to "True", this needs to be changed to "False" for any referenced pipeline component. Let's see an example of that.

The following figure shows this for the Contract DLL when referenced within the HostSideAdapter project:

The Code

OK, OK, enough talk, you want to see some code, right? There are a lot of different projects, but the code is fairly simple I think. I'll go through each of them in turn.

Contract

This simply contains two interfaces that are implemented by the host and AddIn side adapters:

namespace Contract
{
    /// <summary>
    /// The actual AddIn contract that is implemented by the
    /// <see cref="AddInSideAdapter.NumberProcessorViewToContractAdapter">AddIn Adapter</see>
    /// </summary>
    [AddInContract]
    public interface INumberProcessorContract : IContract
    {
        List<int> ProcessNumbers(int fromNumber, int toNumber);
        void Initialize(IHostObjectContract hostObj);
    }

    /// <summary>
    /// The actual Host contract that is implemented by the
    /// <see cref="HostInSideAdapter.HostObjectViewToContractHostAdapter">Host Adapter</see>
    /// </summary>
    public interface IHostObjectContract : IContract
    {
        void ReportProgress(int progressPercent);
    }
}

AddIn Adapter Class

This simply contains two classes. The NumberProcessorViewToContractAdapter class allows the AddIn adapter to to interact with the View and the Contract. Whilst the HostObjectContractToViewAddInAdapter class allows the AddIn adapter to interact with the Host view. In this case, this allows the AddIn to report progress.

namespace AddInSideAdapter
{

    /// <summary>
    /// Adapter use to talk to AddIn
    /// <see cref="Contract.INumberProcessorContract">AddIn Contract</see>
    /// </summary>
    [AddInAdapter]
    public class NumberProcessorViewToContractAdapter : ContractBase, 
                 Contract.INumberProcessorContract
    {
      .......
      .......
    }

    /// <summary>
    /// Allows AddIn adapter to talk back to HostView
    /// </summary>
    public class HostObjectContractToViewAddInAdapter : AddInView.HostObject
    {
      .......
      .......
    }
}

Host Adapter Class

This simply contains two classes. The NumberProcessorContractToViewHostAdapter class allows the Host side adapter to interact with the host View. Whilst the HostObjectViewToContractHostAdapter class allows the Host adapter to interact with the Host view, which in this case allows the AddIn to report progress.

namespace HostSideAdapter
{

    /// <summary>
    /// Adapter use to talk to <see
    /// cref="HostView.NumberProcessorHostView">Host View</see>
    /// </summary>
    [HostAdapter]
    public class NumberProcessorContractToViewHostAdapter : 
                 HostView.NumberProcessorHostView
    {
      .......
      .......
    }


    /// <summary>
    /// Allows Host side adapter to talk back to HostView
    /// </summary>
    public class HostObjectViewToContractHostAdapter : ContractBase, 
                 Contract.IHostObjectContract
    {
      .......
      .......
    }
}

AddIn View Class

This simply contains two abstract classes. The NumberProcessorAddInView class is inherited by any AddIn concrete class. Whilst the HostObject is inherited by an object that needs to communicate between the host Contract and View adapter.

namespace AddInView
{    
    /// <summary>
    /// Abstract base class that should be inherited by all AddIns
    /// </summary>
    [AddInBase]
    public abstract class NumberProcessorAddInView
    {
        public abstract List<int> ProcessNumbers(int fromNumber, int toNumber);

        public abstract void Initialize(HostObject hostObj);
    }

    /// <summary>
    /// Abstract class that should be inherited by an object that needs to communicate
    /// between the host Contract to View adapter
    /// <see cref="AddInSideAdapter.HostObjectContractToViewAddInAdapter">
    /// HostObjectContractToViewAddInAdapter</see>
    /// </summary>
    public abstract class HostObject
    {
        public abstract void ReportProgress(int progressPercent);
    }
}

Host View Class

This simply contains two abstract classes. The NumberProcessorHostView class is inherited by any host side adapter concrete class. Whilst the HostObject is inherited by a class within the host application that can make use of the reported progress.

namespace HostView
{
    /// <summary>
    /// Abstract base class that should be inherited by the Host view
    /// </summary>
    public abstract class NumberProcessorHostView
    {
        public abstract List<int> ProcessNumbers(int fromNumber, int toNumber);

        public abstract void Initialize(HostObject host);
    }

    /// <summary>
    /// Abstract base class that should be inherited by a class within the host
    /// application that can make use of the reported progress
    /// </summary>
    public abstract class HostObject
    {
        public abstract void ReportProgress(int progressPercent);
    }
}

Host Application

This is the WinForms or WPF (WPF, in this case) application that is hosting the AddIns found. It is also responsible for calling the selected add-in's methods, and allows the add-in to report progress by using an internal class AutomationHost which inherits from the HostView.HostObject class.

namespace ApplicationHost
{
    /// <summary>
    /// The main host application. Simply shows a list of AddIns and the 
    /// results of running the Addin
    /// </summary>
    public partial class Window1 : Window
    {
        #region Data
        private AutomationHost automationHost;
        private HostView.NumberProcessorHostView addin;
        #endregion

      .......
      .......

        #region Private Methods
        /// <summary>
        /// Loads a list of all available AddIns
        /// </summary>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {                     
            string path = Environment.CurrentDirectory;            
            AddInStore.Update(path);

            string[] s = AddInStore.RebuildAddIns(path);

            IList<AddInToken> tokens = 
              AddInStore.FindAddIns(typeof(HostView.NumberProcessorHostView), path);
            lstAddIns.ItemsSource = tokens;
            automationHost = new AutomationHost(progressBar);
        }


        /// <summary>
        /// Use the selected AddIn
        /// </summary>
        private void btnUseAddin_Click(object sender, RoutedEventArgs e)
        {
            if (lstAddIns.SelectedIndex != -1)
            {
                // get selected addin
                AddInToken token = (AddInToken)lstAddIns.SelectedItem;
                addin = 
                  token.Activate<HostView.NumberProcessorHostView>(
                  AddInSecurityLevel.Internet);
                addin.Initialize(automationHost);

                // process addin on new thread
                Thread thread = new Thread(RunBackgroundAddIn);
                thread.Start();
            }
            else
                MessageBox.Show("You need to select an addin first");
        }

        /// <summary>
        /// Runs Selected AddIn new thread
        /// </summary>
        private void RunBackgroundAddIn()
        {            
            // Do the work.
            List<int> numbersProcessed = addin.ProcessNumbers(1, 20);
            
            // update UI on UI thread
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    (ThreadStart)delegate()
                    {
                        lstNumbers.ItemsSource = numbersProcessed;
                        progressBar.Value = 0;

                        // Release the add-in
                        addin = null;
                    }
                );                            
        }

      .......
      .......
    }

    /// <summary>
    /// A wrapper class that allows the reported progress within the
    /// <see cref="HostView.HostObject">host view </see> to display
    /// progress on a ProgressBar within the host app
    /// </summary>
    internal class AutomationHost : HostView.HostObject
    {
        .......
        .......
    }
}

As I stated, this is WPF (but could be WinForms); the UI is not important and is a throw away. However, it looks like this:

Where there is a list of add-ins found, and then the user is able to apply the add-ins. This probably needs a little bit of an explanation. There is a class called AddInStore which allows the .NET code to rebuild the add-in list. This results in a new file being created on the file system.

The AddInStore also allows the app code to find add-ins. So this is exactly what I do with the following lines, where the add-ins are refreshed and then added to a ListBox:

string path = Environment.CurrentDirectory;            
AddInStore.Update(path);

string[] s = AddInStore.RebuildAddIns(path);

IList<AddInToken> tokens = 
  AddInStore.FindAddIns(typeof(HostView.NumberProcessorHostView), path);

So that really only leaves the add-in concrete classes themselves; these are fairly trivial (you'll be pleased to hear... as the add-in model is fairly scary, and not for the faint hearted, and without Mathew McDonald's excellent book on WPF, I could not have written this article). So, let's see the add-in's code.

The Actual AddIns

Recall that there were three add-ins, each of which inherits from the abstract AddInView.NumberProcessorAddInView class.

  • Finobacci numbers add-in: Returns a list of Fibonacci numbers between fromNumber and toNumber.
  • Loop numbers add-in: Returns a list of numbers between fromNumber and toNumber.
  • Prime numbers add-in: Returns a list of Prime numbers between fromNumber and toNumber.

Well, they all roughly work the same. I will put the code from them all for completeness, though this is the easy part.

Finobacci Numbers AddIn

Uses recursion to return a list of Fibonacci numbers between fromNumber and toNumber.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;

namespace FibonacciAddIn
{
    [AddIn("Fibonacci Number Processor", Version = "1.0.0.0", 
           Publisher = "Sacha Barber", 
           Description = "Returns an List<int> of fiibonacci number integers within the " +
            "to/from range provided to the addin")]
    public class FibonacciNumberProcessor : AddInView.NumberProcessorAddInView
    {
        private AddInView.HostObject host;

        public static int Fibonacci(int n)
        {
            if (n == 0 || n == 1)
                return n;
            else
                return Fibonacci(n - 1) + Fibonacci(n - 2);
        }

        public override List<int> ProcessNumbers(int fromNumber, int toNumber)
        {
            List<int> results = new List<int>();

            double factor = 100 / toNumber - fromNumber;

            for (int i = fromNumber; i < toNumber; i++)
            {
                host.ReportProgress((int)(i * factor));
                results.Add(Fibonacci(i));
            }
            host.ReportProgress(100);
            return results;
        }


        public override void Initialize(AddInView.HostObject hostObj)
        {
            host = hostObj;
        }
    }
}

Loop Numbers AddIn

Returns a list of of numbers between fromNumber and toNumber.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;

namespace LoopAddIn
{
    [AddIn("Loop Number Processor", Version = "1.0.0.0", Publisher = "Sacha Barber",
        Description = "Returns an List<int> of looped number integers within the " +
                "to/from range provided to the addin")]
    public class LoopNumberProcessor : AddInView.NumberProcessorAddInView
    {

        private AddInView.HostObject host;

        public override List<int> ProcessNumbers(int fromNumber, int toNumber)
        {
            List<int> results = new List<int>();

            double factor = 100 / toNumber - fromNumber;

            for (int i = fromNumber; i < toNumber; i++)
            {
                host.ReportProgress((int)(i * factor));
                results.Add(i);
            }
            host.ReportProgress(100);
            return results;
        }

        public override void Initialize(AddInView.HostObject hostObj)
        {
            host = hostObj;
        }
    }
}

Prime Numbers AddIn

Returns a list of of prime numbers between fromNumber and toNumber.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.AddIn;
using System.Threading;


namespace PrimeNumberAddIn
{
    [AddIn("Prime Number Processor", Version = "1.0.0.0", Publisher = "Sacha Barber",
        Description = "Returns an List<int> of prime number integers within the" +
            "to/from range provided to the addin")]
    public class PrimeNumberProcessor : AddInView.NumberProcessorAddInView 
    {

        private AddInView.HostObject host;

        public override List<int> ProcessNumbers(int fromNumber, int toNumber)
        {
            List<int> results = new List<int>();
            int[] list = new int[toNumber - fromNumber];
            double factor = 100 / toNumber - fromNumber;

            // Create an array containing all integers between the two specified numbers.
            for (int i = 0; i < list.Length; i++)
            {
                list[i] = fromNumber;
                fromNumber += 1;
            }

            //find out the module for each item in list, divided by each d, where
            //d is < or == to sqrt(to)
            //mark composite with 1, and primes with 0 in mark array
            int maxDiv = (int)Math.Floor(Math.Sqrt(toNumber));

            int[] mark = new int[list.Length];

            for (int i = 0; i < list.Length; i++)
            {
                for (int j = 2; j <= maxDiv; j++)
                    if ((list[i] != j) && (list[i] % j == 0))
                        mark[i] = 1;

                host.ReportProgress((int)(i * factor));
            }

            //get the marked primes from original array
            for (int i = 0; i < mark.Length; i++)
                if (mark[i] == 0)
                    results.Add(list[i]);

            host.ReportProgress(100);
            return results;
        }

        public override void Initialize(AddInView.HostObject hostObj)
        {
            host = hostObj;
        }
    }
}

And here is a screenshot of the add-ins running inside a WPF app (I have made it look nice with a WPF DataTemplate, but that's not important):

AddIn Help

As you can see, this is a complicated arrangement, and it is quite a lot to get your head around, and quite a lot of work. The CLR AddIn team has listened to initial concerns that this model is too much work, and have dedicated a CodePlex page to a Visual Studio AddIn that aids in the development of creating AddIn enabled apps. They also supply a blank AddIn pipeline project for you to start with. The VS AddIn aids in the creation of both the host and AddIn side adapters. The CLR AddIn team's CodePlex page can be found at the following link: Managed Extensibility and Add-In Framework.

Conclusion

I think it's fair to say that the AddIn model is not that easy to understand, but this example shows you most of what you will need to know. I have not included all the code in this article, I have tried to only include what I consider to be the most important parts (Nish, if you are reading, see I've listened to you) of the code. As such, you will need to refer to the article's demo application in order to understand the full workings of the AddIn model. I hope you learnt something from this article, and if you liked it, please leave a vote for it.

History

  • V1.0: 07/05/08.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionMy vote of 5 PinmemberW.S.A22-Aug-13 23:38 
QuestionIs this possible if my add-in is present in a remote location? PinmemberSandeep Gunnam23-Feb-12 0:10 
AnswerRe: Is this possible if my add-in is present in a remote location? PinmvpSacha Barber23-Feb-12 1:40 
GeneralAddin Returning a User Defined Type PinmemberDFBradshaw16-Jul-10 2:46 
GeneralRe: Addin Returning a User Defined Type PinmvpSacha Barber16-Jul-10 2:47 
GeneralRe: Addin Returning a User Defined Type PinmemberDFBradshaw16-Jul-10 3:12 
GeneralRe: Addin Returning a User Defined Type PinmvpSacha Barber16-Jul-10 3:48 
GeneralRe: Addin Returning a User Defined Type PinmvpSacha Barber16-Jul-10 2:49 
GeneralRe: Addin Returning a User Defined Type PinmemberDFBradshaw17-Jul-10 4:11 
GeneralRe: Addin Returning a User Defined Type PinmvpSacha Barber17-Jul-10 8:15 
GeneralRe: Addin Returning a User Defined Type PinmemberDFBradshaw21-Jul-10 5:49 
GeneralRe: Addin Returning a User Defined Type PinmvpSacha Barber21-Jul-10 5:53 
GeneralSystem.AddIn is too hard to go Pinmemberabdurahman ibn hattab20-Feb-10 20:59 
GeneralRe: System.AddIn is too hard to go PinmvpSacha Barber21-Feb-10 20:01 
QuestionRe: System.AddIn is too hard to go PinmemberGreg Cadmes15-Mar-10 11:03 
AnswerRe: System.AddIn is too hard to go PinmvpSacha Barber15-Mar-10 11:21 
NewsMSDN instructions are better PinmemberPie At The Raven27-Jan-10 11:30 
GeneralRe: MSDN instructions are better PinmvpSacha Barber27-Jan-10 21:42 
GeneralRe: MSDN instructions are better PinmemberPie At The Raven1-Feb-10 10:42 
GeneralRe: MSDN instructions are better PinmvpSacha Barber1-Feb-10 21:51 
GeneralRe: MSDN instructions are better PinmemberLuka23-Nov-10 4:28 
GeneralRe: MSDN instructions are better PinmvpSacha Barber1-Dec-10 19:58 
QuestionWhat about using Mono.Addin ? Pinmemberalex.jarnoux3-Apr-09 0:14 
AnswerRe: What about using Mono.Addin ? PinmvpSacha Barber3-Apr-09 0:23 
GeneralRebuilding the pipeline PinmemberKristjan Birgisson3-Mar-09 11:26 
Great article, very useful. Thanks alot.
 
I was wondering about all the warnings that are produced when you rebuild the pipeline with the code in Window1.xaml.cs :
 
string[] s = AddInStore.RebuildAddIns(path);
 
There are lot of warnings like this :
"Could not find a valid add-in in the directory C:\\Users\\kristjan\\Desktop\\Projects-Addin\\CP_AddInTest\\Output\\AddIns."
 

Is this a bug, or is this normal? The program builds normally and there seems to be no problems. Smile | :)
 
Best regards
Kristján

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140926.1 | Last Updated 7 May 2008
Article Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid