I want to create a Group

Topics: Developer Forum
Apr 12, 2010 at 8:45 PM

I am on Windows 7 and have been trying to create a group. I have been able to set the ContactType to group, which saves it as a .group file. However, when I double click on it and open it in Windows 7, it doesn't show the members I added to that group.

I have created a group using the Windows UI, added members, and saved it. When I look at the group file in a text editor, it looks like all the member list is saved in a binary field.

I have used MapiGroupView to read a group file created in windows. This works correctly, and I can see the members using MapiGroupView. However, I do not see how to change the members of the MapiGroupView and save it back out.

Any help would be greatly appreciated.

Coordinator
Apr 12, 2010 at 9:26 PM

The in-box UI for Windows Contacts doesn't deal with groups very well.  It's still based on the MAPI based WAB APIs and stores group data in a binary format that my library can read from, but doesn't serialize to.  This is all stuff that you've already figured out :)

The primary reason I don't round-trip back to the MAPI version of groups is that no one has asked for the functionality.  The MAPI implementation of groups is pretty different from what it should be with the XML backed contact data.  There would potentially be some ambiguities, but it's possible to roundtrip changes from one to the other.

What is the scenario where you need the groups to be available to the user through the in-box UI?  The UI for a contact or group is really simple to recreate using WPF or WinForms (the source has a sample of it for at least Contacts).  If at all possible what I'd recommend doing is replacing the file handlers for Contacts and Groups with your own application that will do the conversion to a PersonCollection and save off the MapiGroupView properties as cleaner XML.  You don't want to nuke the original list in case the user or some other program is interacting with these groups from the MAPI APIs.

Apr 12, 2010 at 9:57 PM

Honestly, I don't know much about contacts or the different versions, such as MAPI. Here is what I am trying to do:

I have a large list of people with various information about each person. I have used Contacts.Net to create a contact for each person, which worked really well! I now want to group these people/contacts in different ways. Once I have grouped them I will be syncing the contacts and groups to my iPod Touch. This is very much a one way process. Each time I run this process, I will probably nuke the previous list of contacts and groups.

I've looked through the MapiViewGroup code and see that the binary format for the contact list is pretty straight forward. I'm going to write a little code to build the contact list as a binary field. I don't think this will take too long, and it should get me to where I want to be.

If there is a newer group format that I should be using that would still sync with my iPod Touch, I would be happy to use that also.

Thanks for the help and the nice work on this project!

Coordinator
Apr 12, 2010 at 10:36 PM

I didn't realize iPod had APIs to pull contacts from Windows.  They're probably using the MAPI APIs, so you'll need to continue to work with that format.

I think you're right and best off just modifying my MapiGroupView code to support writing out to those properties.  Looking at the code I think it should be straightforward, but I don't remember what's in that 24 bytes of junk in the one-off case so you might need to reverse engineer that.  I can add the functionality to the library also, but I'm not sure when I'll have time to.  If for your purposes you can just write to the one property that has real contacts referenced (instead of the name/email pair) then this should be easy.

I can try to help if you run into any issues doing this.

Good luck!

Apr 12, 2010 at 10:56 PM

I was able to generate the single binary field and am ignoring the name/email pair field. It seems to work great opening it in windows. So, I assume it will work fine on my iPod Touch.

Thanks for all the help!

Dec 19, 2011 at 8:47 AM

Please can you share the code with me, beacuse I have a similar dilema and I don't know my way through...regards

Coordinator
Dec 22, 2011 at 12:03 AM

I just checked in a change (94885) that adds ReplaceMemberIds and ReplaceOneOffMembers to MapiGroupView.  I'd only done really rudimentary testing of it, but I think it's right.  I've checked at least that the WAB is able to read groups that used the replace functions for those properties.

Hope that helps,

Dec 22, 2011 at 10:49 AM
Edited Dec 22, 2011 at 10:50 AM

Thank you so much.

In my attempt to get solution I used another library to write members to the group (NKTWABLib;)

We have over four thousand contacts and  writing to the group using the NKTWABLib was slow and memory consuming (about 1 gigabytes at times). I employed Application.DoEvents to help.

Now I downloaded the Change Set 94885 but it contains four folders.

  1. BuildProcessTemplates
  2. Contacts
  3. Contacts-1.0
  4. ContactsBridge

 I dont know what each of them is meant for.(Contacts and Contacts-1.0 seem to be similar )  can you please explain.

Regards

Dec 22, 2011 at 12:57 PM

Just an information:

I cant open the solution file Contacts.sln with visual studio 2008 (I'm working with 2008)

Coordinator
Dec 22, 2011 at 7:29 PM

Contacts-1.0 is a forked snapshot of the last major release.  The contacts folder has the changes you want.  I think other than the one you just requested the only other major change since the 1.0 release was better Unicode support for vcards.  The solution is based on vs2010 and targets .Net 4.0.  it's probably easiest to just take the newest version of the MapiGroupView file, change the namespace, and then use that directly from your project

Dec 23, 2011 at 5:04 PM
Edited Dec 28, 2011 at 2:33 PM

Please can you kindly me me look at this code if its efficient?    

            //Create Group      

ContactTypes Group_type = ContactTypes.Group;
ContactManager cm = new ContactManager();          

 Microsoft.Communications.Contacts.Contact c = cm.CreateContact(Group_type);
c.Names.Default = new Name("MyContactGroup");
c.CommitChanges(ContactCommitOptions.IgnoreChangeConflicts);

try
                {
                    int intCounter = 0;
                    int intRejectCounter = 0;
                    valuePairs = new Dictionary<string, string>();

                    totalStaff = 0;
                    Microsoft.Communications.Contacts.MapiGroupView mapiGroupView = new MapiGroupView(c);
                    Microsoft.Communications.Contacts.GroupView GroupView = new GroupView(c);
                    List<Person> oneOffs = new List<Person>();
                    foreach (DataRow myRow in dt.Rows) //get data from database contacts table
                    {
                        intCounter = intCounter + 1;
                        Microsoft.Communications.Contacts.Contact contact = cm.CreateContact();
                        if ((myRow != null))
                        {
                            if (SetContact(contact, myRow) > 0)
                            {
                                contact.CommitChanges(ContactCommitOptions.IgnoreChangeConflicts);
                                c.ContactIds.Add(contact.ContactIds.Default);
                                totalStaff = totalStaff + 1;
                                valuePairs.Add(totalStaff.ToString(), contact.Id.ToString());
                            }
                            else {
                                intRejectCounter = intRejectCounter + 1;
                            }
                            if (g_Mode != "SILENT") { this.backgroundWorker1.ReportProgress(-1, string.Format("Analyzing {0} Tucn contacts, Created {1} Rejected {2}, Scrutinized {3}...", dt.Rows.Count, totalStaff, intRejectCounter, (totalStaff + intRejectCounter))); }
                        }
                        if (intCounter % 20 == 0)
                        {
                            if (backgroundWorker1.CancellationPending)
                            {
                                return;
                            }
                            Application.DoEvents();
                        }
                    }
                    if (totalStaff > 0)
                    {
                        try
                        {
                            var contactIds = new string[totalStaff];
                            int i = 0;
                            foreach (KeyValuePair<string, string> pair in valuePairs)
                            {
                                if (!string.IsNullOrEmpty(pair.Value))
                                {
                                    contactIds[i] = pair.Value;
                                    i = i + 1;
                                    if (g_Mode != "SILENT") { this.backgroundWorker1.ReportProgress(-1, string.Format("Registering {1} of {0} contacts with MyContactGroup...", totalStaff, i)); }
                                }
                                if (i % 20 == 0)
                                {
                                    Application.DoEvents();
                                    if (backgroundWorker1.CancellationPending)
                                    {
                                        return;
                                    }
                                }
                            }
                            //write to group
                            mapiGroupView.ReplaceMemberIds(contactIds);
                            c.CommitChanges(ContactCommitOptions.IgnoreChangeConflicts);
                            this.backgroundWorker1.ReportProgress(100, string.Format("Done"));
                            this.labelProgress.Text = "Operation finished successfuly!";
                            this.pictureBox.Image = Properties.Resources.InformationImage;
                        }
                        catch (Exception ex)
                        {
                            LogEvent(ex.Message + ",  Create contacts and groups process");
                        }
                    }
                }
                catch (Exception ex)
                {
                    LogEvent(ex.Message + " ");
                }

Dec 28, 2011 at 2:34 PM

Please can you kindly me me look at the code above if its efficient?   

Coordinator
Dec 28, 2011 at 6:35 PM
Tasker wrote:

Please can you kindly me me look at the code above if its efficient?   

I didn't realize you were looking for feedback on it.  That snippet seems like a mostly correct use of the APIs.  The only thing wrong is the line:

c.ContactIds.Add(contact.ContactIds.Default);

You're adding the IDs of the contacts members to the IDs of the group, so if you request a Contact with any of those IDs you may get the group instead of the contact.  Other than that this looks correct as much as I can tell from reading it (I don't know how SetContact is implemented).  I'm not sure whether with what you're doing if you expect the import to fail, in which case you may be leaving behind a partial set of imported contacts and an empty group.

With regards to efficiency rather than correctness, it depends on the bigger context of what you're doing.  You might consider spinning off a dedicated thread rather than periodically calling Application.DoEvents().

Hope that helps,

Feb 7, 2012 at 2:36 PM

Thank you for the reply. I have implemeted the changes. I now use

if(contact != null) c.People.Add(contact);

 

 

 

  I have a problem with threading - when i try to thread it gives me this message :-

System.InvalidOperationException was caught
  Message="Contacts can only be created on STA threads and can only be accessed from the thread on which they were created."
  Source="Contacts"
  StackTrace:
       at Standard.Verify.IsApartmentState(ApartmentState requiredState, String message) in C:\Local\Projects - testupdate\Standard\Verify.cs:line 46
       at Microsoft.Communications.Contacts.ContactManager..ctor(String rootDirectory, Boolean recurseSubfolders) in C:\Local\Projects - testupdate\Contacts\ContactManager.cs:line 322
       at Microsoft.Communications.Contacts.ContactManager..ctor() in C:\Local\Projects - testupdate\Contacts\ContactManager.cs:line 289
       at ISTTucnwab.frmMain.DeleteOld_CreateGroup() in C:\Local\Projects - testupdate\ISTTucnwab\ISTTucnwab\Form1.cs:line 1544
       at ISTTucnwab.frmMain.KeepAlive() in C:\Local\Projects - testupdate\ISTTucnwab\ISTTucnwab\Form1.cs:line 82
  InnerException:

 This is the code

call thread

 t = new Thread( (new ThreadStart( KeepAlive)));
   t.Name = "Second thread";
   timer1.Enabled = true;
   try { t.Start();}
   catch (Exception ex) { reportException(ex);
}

==============

private void KeepAlive()
{
  try
   {
    DeleteOld_CreateGroup();
     }
    catch (Exception ex) { reportException(ex); }
}

 =========================

 

 

 Threaded_Process(DataTable dt)

        public void DeleteOld_CreateGroup()
        {
            ContactTypes Group_type = ContactTypes.Group;
            ContactManager cm = new ContactManager();
            Microsoft.Communications.Contacts.Contact old_contacts = null;

            //Check if TUCN group already exist in users folder
            // bool bolfoundTucn = false;
            turnOff = false;
            try
            {
                foreach (Microsoft.Communications.Contacts.Contact contact in new ContactManager().GetContactCollection(Group_type))
                {
                    if (contact.Names.Default != null)
                    {
                        if (contact.Names.Default.ToString() == "Tucn")
                        {
                            old_contacts = contact;
                            Microsoft.Communications.Contacts.MapiGroupView mapiGroupView = new MapiGroupView(old_contacts);
                            if (mapiGroupView.MemberIds.Count > 0)
                            {
                                int counter = 0;
                                int totalmembers = mapiGroupView.MemberIds.Count;
                                foreach (string id in mapiGroupView.MemberIds)
                                {
                                    cm.Remove(id.ToString());
                                    counter = counter + 1;
                                    if (g_Mode != "SILENT") { this.backgroundWorker1.ReportProgress(-1, string.Format("Deleting {1} of {0} old Tucn contacts...", totalmembers, counter)); }
                                    if (counter % 20 == 0)
                                    {
                                        if (backgroundWorker1.CancellationPending)
                                        {
                                            return;
                                        }
                                      /////  Application.DoEvents();
                                    }
                                }
                            }
                            cm.Remove(old_contacts.Id);
                        }
                    }
                }

         } 
            catch (Exception )//ex)
            {
               /// LogEvent(ex.Message + ", Get Data from Metadirectory process ");
            }
            finally
            {
                cm.Dispose();
            }

 

 }

 

Coordinator
Feb 7, 2012 at 5:31 PM

Call "t.SetApartmentState(ApartmentState.STA)" before starting your thread.

You can read more about what that actually does on MSDN or StackOverflow, but really I probably shouldn't be enforcing that for contact objects.  If you just remove the lines that are throwing those exceptions from your copy of the code things should also work fine.

 

Coordinator
Feb 7, 2012 at 5:38 PM

Also a couple small comments on your code snippet:

* You might consider using "using(var cm = new ContactManager()){}" rather than the try {} finally { cm.Dispose(); } pattern.

* The check for if(contact.Names.Default != null) is redundant, because the Name type is a struct instead of a class.  Structs can't be assigned to null so that check will always evaluate to true.

Those don't actually affect the correctness of what you wrote, but I thought it may be helpful for you to know.

Feb 13, 2012 at 2:31 PM

Hi JoeCastro, thank you for your immense support.

I have implemented you suggestions and the application is working great!

I still need your assistance. We have about 5 thousand contacts here. The program deletes these contacts and creates new once each time it is run for updates.

The issues is that I have been asked to back-up the contacts before replacing them such that if the replacment fails i can restore the back-up.

How can i implement this? Please assist

Regards

Coordinator
Feb 13, 2012 at 4:01 PM

You're probably best off copying the files for the contacts to a temporary backup directory.  Any saved contact is backed by a .contact file that you can get the path for.  You also probably could create a backup contactmanager object rooted at a temp directory and add contacts to that as you're deleting them, but I think the way that would be implemented is slower than just copying the files if you aren't concerned about backing up any new changes.

I hope that helps.