/// <summary>
        /// Called when a subscription is added or removed from the control.
        /// </summary>
        public void OnSubscriptionModified(TsCDaSubscription subscription, bool deleted)
        {
            if (subscription == null)
            {
                return;
            }

            if (!deleted)
            {
                // check if the subscription is already added to the control.
                if (m_items.Contains(subscription.ClientHandle))
                {
                    return;
                }

                // register for data updates.
                subscription.DataChangedEvent += new TsCDaDataChangedEventHandler(OnDataChangeEvent);

                // add to subscription list.
                m_subscriptions.Add(subscription.ClientHandle, subscription);
                m_items.Add(subscription.ClientHandle, new ArrayList());
            }
            else
            {
                // check if the subscription is already removed from the control.
                if (!m_items.Contains(subscription.ClientHandle))
                {
                    return;
                }

                // unregister for data updates.
                subscription.DataChangedEvent -= new TsCDaDataChangedEventHandler(OnDataChangeEvent);

                // remove all items for the subscription.
                ArrayList existingItemList = (ArrayList)m_items[subscription.ClientHandle];

                foreach (ListViewItem listItem in existingItemList)
                {
                    EditComplexValueDlg dialog = (EditComplexValueDlg)m_viewers[listItem];

                    if (dialog != null)
                    {
                        dialog.Close();
                        m_viewers.Remove(listItem);
                    }

                    listItem.Remove();
                }

                // remove from subscription list.
                m_subscriptions.Remove(subscription.ClientHandle);
                m_items.Remove(subscription.ClientHandle);
            }
        }
        /// <summary>
        /// Shows a detailed view for array values.
        /// </summary>
        private void ViewMI_Click(object sender, System.EventArgs e)
        {
            if (ItemListLV.SelectedItems.Count > 0)
            {
                ListViewItem listItem = ItemListLV.SelectedItems[0];

                object tag = listItem.Tag;

                if (tag != null && tag.GetType() == typeof(TsCDaItemValueResult))
                {
                    TsCDaItemValueResult item = (TsCDaItemValueResult)tag;

                    if (item.Value != null)
                    {
                        TsCCpxComplexItem complexItem = TsCCpxComplexTypeCache.GetComplexItem(item);

                        if (complexItem != null)
                        {
                            EditComplexValueDlg dialog = (EditComplexValueDlg)m_viewers[listItem];

                            if (dialog == null)
                            {
                                m_viewers[listItem] = dialog = new EditComplexValueDlg();
                                dialog.Disposed    += new EventHandler(OnViewerClosed);
                            }

                            dialog.ShowDialog(complexItem, item.Value, true, false);
                        }

                        else if (item.Value.GetType().IsArray)
                        {
                            new EditArrayDlg().ShowDialog(item.Value, true);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Adds an item to the list view.
        /// </summary>
        private void AddValue(object subscriptionHandle, TsCDaItemValueResult item, ref ListViewItem replaceableItem)
        {
            string quality = "";

            // format quality.
            if (item.QualitySpecified)
            {
                quality += item.Quality.QualityBits.ToString();

                if (item.Quality.LimitBits != TsDaLimitBits.None)
                {
                    quality += ", ";
                    quality += item.Quality.LimitBits.ToString();
                }

                if (item.Quality.VendorBits != 0)
                {
                    quality += String.Format(", {0:X}", item.Quality.VendorBits);
                }
            }

            // format columns.
            string[] columns = new string[]
            {
                item.ItemName,
                item.ItemPath,
                OpcClientSdk.OpcConvert.ToString(item.Value),
                (item.Value != null)?OpcClientSdk.OpcConvert.ToString(item.Value.GetType()):"",
                quality,
                (item.TimestampSpecified)?OpcClientSdk.OpcConvert.ToString(item.Timestamp):"",
                GetErrorText(subscriptionHandle, item.Result)
            };

            // update an existing item.
            if (replaceableItem != null)
            {
                for (int ii = 0; ii < replaceableItem.SubItems.Count; ii++)
                {
                    replaceableItem.SubItems[ii].Text = columns[ii];
                }

                replaceableItem.Tag       = item;
                replaceableItem.ForeColor = Color.Empty;

                // update detail view dialog.
                EditComplexValueDlg dialog = (EditComplexValueDlg)m_viewers[replaceableItem];

                if (dialog != null)
                {
                    dialog.UpdateValue(item.Value);
                }

                return;
            }

            // create a new list view item.
            replaceableItem     = new ListViewItem(columns, Resources.IMAGE_YELLOW_SCROLL);
            replaceableItem.Tag = item;

            // insert after any existing item value with the same client handle.
            for (int ii = ItemListLV.Items.Count - 1; ii >= 0; ii--)
            {
                TsCDaItemValueResult previous = (TsCDaItemValueResult)ItemListLV.Items[ii].Tag;

                if (previous.ClientHandle != null && previous.ClientHandle.Equals(item.ClientHandle))
                {
                    ItemListLV.Items.Insert(ii + 1, replaceableItem);
                    return;
                }
            }

            ItemListLV.Items.Add(replaceableItem);
        }
        /// <summary>
        /// Called when a data update event is raised by a subscription.
        /// </summary>
        private void OnDataChangeEvent(object subscriptionHandle, object requestHandle, TsCDaItemValueResult[] values)
        {
            // ensure processing is done on the control's main thread.
            if (InvokeRequired)
            {
                BeginInvoke(new TsCDaDataChangedEventHandler(OnDataChangeEvent), new object[] { subscriptionHandle, requestHandle, values });
                return;
            }

            try
            {
                // find subscription.
                ArrayList existingItemList = (ArrayList)m_items[subscriptionHandle];

                // check if subscription is still valid.
                if (existingItemList == null)
                {
                    return;
                }

                // change all existing item values for the subscription to 'grey'.
                // this indicates an update arrived but the value did not change.
                foreach (ListViewItem listItem in existingItemList)
                {
                    if (listItem.ForeColor != Color.Gray)
                    {
                        listItem.ForeColor = Color.Gray;
                    }
                }

                // do nothing more if only a keep alive callback.
                if (values == null || values.Length == 0)
                {
                    OnKeepAlive(subscriptionHandle);
                    return;
                }
                else
                {
                    if (UpdateEvent != null)
                    {
                        UpdateEvent(subscriptionHandle, values);
                    }
                }

                // update list view.
                ArrayList newListItems = new ArrayList();

                foreach (TsCDaItemValueResult value in values)
                {
                    // item value should never have a null client handle.
                    if (value.ClientHandle == null)
                    {
                        continue;
                    }

                    // this item can be updated with new values instead of inserting/removing items.
                    ListViewItem replacableItem = null;

                    // remove existing items.
                    if (!KeepValuesMI.Checked)
                    {
                        // search list of existing items for previous values for this item.
                        ArrayList updatedItemList = new ArrayList(existingItemList.Count);

                        foreach (ListViewItem listItem in existingItemList)
                        {
                            TsCDaItemValueResult previous = (TsCDaItemValueResult)listItem.Tag;

                            if (!value.ClientHandle.Equals(previous.ClientHandle))
                            {
                                updatedItemList.Add(listItem);
                                continue;
                            }

                            if (replacableItem != null)
                            {
                                EditComplexValueDlg dialog = (EditComplexValueDlg)m_viewers[replacableItem];

                                if (dialog != null)
                                {
                                    dialog.Close();
                                    m_viewers.Remove(replacableItem);
                                }

                                replacableItem.Remove();
                            }

                            replacableItem = listItem;
                        }

                        // the updated list has all values for the current item removed.
                        existingItemList = updatedItemList;
                    }

                    // add value to list.
                    AddValue(subscriptionHandle, value, ref replacableItem);

                    // save new list item.
                    if (replacableItem != null)
                    {
                        newListItems.Add(replacableItem);
                    }
                }

                // add new items to existing item list.
                existingItemList.AddRange(newListItems);
                m_items[subscriptionHandle] = existingItemList;

                // adjust column widths.
                for (int ii = 0; ii < ItemListLV.Columns.Count; ii++)
                {
                    if (ii != VALUE && ii != QUALITY)
                    {
                        ItemListLV.Columns[ii].Width = -2;
                    }
                }
            }
            catch (Exception e)
            {
                OnException(subscriptionHandle, e);
            }
        }