Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Dll first problem: thread safe

Status
Not open for further replies.

tzzdvd

Programmer
Aug 17, 2001
52
IT
Hi

I am trying to manage an external dll.
I would like to create an assembly (later exported in a dll) that encapsulates the management of such dll so that it is "easily" usable (I hope!)
I have a two different problems so I will split them into different post:

first problem: thread safe
my assembly exports a class that manages the external dll
one function of a dll need a callback function as a parameter (in particular one function of the class)
No problem, I use a delegate approach and the call back function is correctly called.
The problem is that such call is not considered at the same thread level of the form that instantiates the class so that if I want to signal the form the raising of such function I have problems touching controls of the forum.
I explain better
Code:
public delegate void DelFuncCallBack(... some parameters ...);
public delegate void Signal();
/// dll manager
public class DllAux
{
    [DllImport("some_dll")]
    private static extern int SomeDllFunc(DelFuncCallBack callback);
    
    public Signal ReturnCallBack;
    
    // function call back
    private void func(... some parameters ...)
    {
        ...
        
        if (ReturnCallBack != null)
            ReturnCallBack();
    }
    
    public DllAux()
    {
    }
    
    public TryDll()
    {
        SomeDllFunc(func);
        // or SomeDllFunc(new DelFuncCallBack(func)); // I have not understood the difference
    }
    ...
}

class myForm : Form
{
    ...
    
    DllAux DllUse = new DllAux();
    
    void UseDLL()
    {
        DllUse.ReturnCallBack = new Signal(SignalHasArrived);
        DllUse.TryDll();
    }
    private void SignalHasArrived()
    {
        myTextBox.Text = "Signal arrived"; // <== problem here
    }
    
    ...
}

The funny thing is that I don't receive any run time error as I received some month ago writing a textbox in a timer or in a serial port event manager
In reality, I receive nothing and the text box is not written

Solution found

Code:
public delegate void WriteIntoTextBox(string str);

class myForm : form
{
    ...
    private void SignalHasArrived()
    {
        if (this.InvokeRequired())
            this.BeginInvoke(new WriteIntoTextBox(WriteTextBox),
                    new object[] { "Signal arrived" }); // this line is always executed
        else
            WriteTextBox("Signal arrived");  // this line is never executed
    }
    
    private void WriteTextBox(string text)
    {
        myTextBox.Text = "Signal has arrived";
    }
    ...
}

My question is: do you know any solution that allows me to use the first approach?
How can I transform such tread unsafe class into a thread safe
I have tried to use a delegate approach (the one shown) and also an event approach creating a class inherited form EventArgs and so on but I had the same problem

I have also tried to inherit my class form INotifyPropertyChanged with no results

Thank you very much

Davide
 
Dear Jason,

I am trying this code in order to understand your suggestion

Code:
namespace TestBinding
{
    interface IBindingService
    {
        IEnumerable<Person> GetData();
    }
    
    public class BindingClass : IBindingService
    {
        private List<Person> group = new List<Person>();
        public BindingClass()
        {
        }

        public void AddPerson(Person p)
        {
            group.Add(p);
        }

        public void RemovePerson(int i)
        {
            if ((i > 0) && (i < group.Count))
            {
                group.RemoveAt(i);
            }
        }

        public void RemovePerson()
        {
            if (group.Count > 0)
            {
                group.RemoveAt(0);
            }
        }

        #region IBindingService Membri di

        public IEnumerable<Person> GetData()
        {
            foreach (Person p in group)
                yield return p;
        }

        #endregion
    }

    public partial class frmBinding : Form
    {
        private System.Windows.Forms.DataGridView dgvBinding;
        private System.Windows.Forms.Button cmdAdd;
        private System.Windows.Forms.Button cmdRemove;
        
        ...     // designer
           
        BindingClass myBindingClass = new BindingClass();

        Random rnd = new Random();

        BindingSource bs;

        public frmBinding()
        {
            InitializeComponent();
            cmdAdd_Click(null, null); // first data entered
            cmdAdd_Click(null, null); // second data entered
            bs = new BindingSource();
            bs.DataSource = myBindingClass.GetData();
            dgvBinding.AutoGenerateColumns = true;
            dgvBinding.DataSource = bs;
        }

        private Person CreateRandomPerson()
        {
            StringBuilder str = new StringBuilder();
            int len = rnd.Next(1, 10);
            for (int i = 0; i < len; i++)
                str.Append((char)(((byte)'a') + rnd.Next(0, 25)));
            return new Person(str.ToString(), rnd.Next(0, 100));
        }

        private void cmdAdd_Click(object sender, EventArgs e)
        {
            myBindingClass.AddPerson(CreateRandomPerson());
        }

        private void cmdRemove_Click(object sender, EventArgs e)
        {
            myBindingClass.RemovePerson();
        }
    }

    public class Person : INotifyPropertyChanged
    {
        private string name = "";
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }


        private int age = 0;
        public int Age
        {
            get { return age; }
            set
            {
                age = value; 
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Age"));
            }
        }

        public Person(string n, int a)
        {
            Name = n;
            Age = a;
        }

        #region INotifyPropertyChanged Membri di

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

The result is that my datagrid displays correctly the columns, the first two "Person" added but there is non reaction to Add or Remove actions.
As you see, I have also added the support to INotifyPropertyChanged because somewhere I have read that this is the only way to make the datagridview sensible to my list changes.
Unfortunatly with no result.
The reason in this piece of code
Code:
if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("Age"));
PropertyChanged == null of course!!!!!

Do you know where to fill this event delegate?

I have done another improvement.
I have changed the List<> into BindingList<> but nothing happens till I make it public and I link it to the DataSource
Code:
bs = new BindingSource();
bs.DataSource = myBindingClass.group; // BindingList<Person>
dgvBinding.AutoGenerateColumns = true;
dgvBinding.DataSource = bs;
or
Code:
dgvBinding.AutoGenerateColumns = true;
dgvBinding.DataSource = myBindingClass.group;  // BindingList<Person>
with the same result: the datagridview is bounded correctly and every time I add or remove a Person these changes are displayed into the control

At this point I remove the IBindingService and the INotifyPropertyChanged support and the result doesn't change.
It seems that the only important thing is the BindingList<>

This is my final solution
Code:
public class BindingClass
{
    private BindingList<Person> group = new BindingList<Person>();

    private BindingSource bs = new BindingSource();

    public object DataSource
    {
        get { return bs.DataSource; }              // in order not to let people to modify this parameter!
    }

    public BindingClass()
    {
        bs.DataSource = group;
    }
    
    public void AddPerson(Person p)
    {
        group.Add(p);
    }
    
    ...
}

public partial class frmBinding : Form
{
    BindingClass myBindingClass = new BindingClass();
    public frmBinding()
    {
        InitializeComponent();
        dgvBinding.DataSource = myBindingClass.DataSource;
    }
    ...
}
and it seems to work as I wish.

Now I am in front of three different problems:
1) how to cusomize my column
2) Can I create a runtime context menu row by row according to some parameters internal to the class Person?
For example (very stupid example) I would like to have a menu "Buy a car" only if the person age is above the legal age to drive it.
The same as for "Drink a beer" but the "Drink some water" always present. Of course if such parameters changes (birthday) the menu must appear according to these changes
This is a problem of Visible/NotVisible of course in a contextmenu always present in the person class.
I don't know how to bind the menu to the row!

3) If the AddPerson function is called for a different thread I receive the cross-thread exception on the form only because of the binding present.

I have to study a little for the first problem
Any suggestion for the other two?

Davide

 
Sorry I post this thread in the wrong position but anyway the last question maybe the key of this thread too
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top