Вот, добрался.
Попробую пошагово показать как я реализовывал данную программку.
- Создаём Windows Forms Application.
- Добавить библиотеку типов - domino.tlb. Для этого в меню Project\Add Reference где в проводнике и находим нужную библиотеку (ищите в месте установке клиента Lotus Notes). Visual Studio сам создаст "обёртку" к данной библиотеке и Вы сможете использовать "родные" для Вас notes-классы.
- Я для себя определил следующюю схему:объявляю глобальную переменную нотес-сессии(Domino.NotesSession nsSession), по желанию пользователя инициирую, а сбор данных веду в отдельном потоке.
- Для сбора данных в отдельном потоке использую отдельный самописный клас(ThreadsElements) который "асинхронно" используется в компоненте
Ссылка скрыта от гостей
- Собранные данные скидываю в компоненту
Ссылка скрыта от гостей
, а при необходимости вывожу всплывающее сообщение из системного трея ( компонента
Ссылка скрыта от гостей
).
Листинг на C# ( Visual Studio 2010 )
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Компоненты - описание</div></div><div class="sp-body"><div class="sp-content">
/*
* imageList1 - компонента, контейнер рисунков(для отображения в сис.трее)
* dtGridMain - компонента-таблица для отображения списка новых сообщений
* backgroundWorker1 - комп.-для работы в отдельном потоке(читать литературу)
* cmsIconMenu - комп.-контекстное меню, для иконки
* btnConnect - кнопка включить
* btnOff - кнопка "свернуть в трей"
* btnClose - кнопка "Выключить и выйти"
* tbCntrlMain -комп.-табуляторный контрол с 2мя закладками ( tabPage1 и tpOptions )
* txtbxServer, txtbxDataBase, txtbxView - комп.-текстбоксы с настройками "куда" смотреть
* comboBox1 - комп.-комбобокс в которой выбираеться временной интервал для запросов
* rbDBlClickChColor, rbDBlClickDelFromGrid - комп.-радобатон для выбора действия при клике на новом сообщении ( менять цвет или удалять из списка)
* chBxHideInTrayOnDblClick - комп.-чекбокс для сворачивания окна программки при открытии ссылки на новое сообщение.
*/
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Обьявление гл.переменных </div></div><div class="sp-body"><div class="sp-content">
C++:
Domino.NotesSession nsSession; // глобальная переменная, для сессии
ThreadsElements asyncObj = new ThreadsElements(); // глобальная переменная для работы в потоке, описан ниже
Boolean mClose = false; // для понимания закрыть программу или свернуть
// Константы - номера иконок для сис. трея. обозначает номер рисунка в imageList1
int iOffLine = 0;
int iOnLine = 5;
int iNewMsg = 1;
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Кнопка включить</div></div><div class="sp-body"><div class="sp-content">
C++:
private void btnConnect_Click(object sender, EventArgs e)
{
if (nsSession == null) {
try // создаём сессию
{ nsSession = new Domino.NotesSession(); }
catch (Exception err)
{
MessageBox.Show(err.Message);
}
}
try // тут может быть ошибка, пользователь отменяет ввод пароля кнопкой Cancel
{ nsSession.Initialize(); } // иницируем, можем сразу передать пароль. например nsSession.Initialize("myPassWord001");
catch (Exception err)
{
nsSession = null;
MessageBox.Show(err.Message);
// Включились в офф-лайн вещание
// nicnMain.Icon = Icon.FromHandle(((Bitmap)imageList1.Images[iOffLine]).GetHicon()); // Пока отключено
}
if (nsSession == null)
{
nicnMain.ShowBalloonTip(300, "Ошибка", "Подключиться не удалось", ToolTipIcon.Error);
nicnMain.Text = "Авто оповещение [Откл.]";
}
else
{
if ( asyncObj.IsConnected == false )
asyncObj = new ThreadsElements() // создаем обьект для работы в отдельном потоке и задаём параметры
{
IsConnected = true,
ServerName = txtbxServer.Text,
DBName = txtbxDataBase.Text,
ViewName = txtbxView.Text,
s = nsSession,
iSleep = System.Convert.ToInt32(comboBox1.Text) ,
IsFirstTimeRun = true
};
// Включились в он-лайн вещание
// nicnMain.Icon = Icon.FromHandle(((Bitmap)imageList1.Images[iOnLine]).GetHicon()); // Пока отключено
nicnMain.Text = "Авто оповещение [Bкл.]";
backgroundWorker1.RunWorkerAsync(asyncObj); // начинаем паралельный поток по сбору данных
tbCntrlMain.TabPages["tpOptions"].Enabled = false; // делаем закладку опций неактивной
tbCntrlMain.SelectedTab = tbCntrlMain.TabPages[0]; // переходим на закладку с таблицей
btnConnect.Enabled = false; // Кнопку Включить "дизейблим", чтобы больше не клацали
Hide(); // Скрываем приложение в трей
}
}
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Кнопка - свернуть в трей</div></div><div class="sp-body"><div class="sp-content">
C++:
private void btnOff_Click(object sender, EventArgs e)
{
Hide();
}
Компонента backgroundWorker1 имеет два события DoWork (зделать чтотоd отдельном потоке) и RunWorkerCompleted(что сделать по завершении выполнения потока)
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">backgroundWorker1</div></div><div class="sp-body"><div class="sp-content">
C++:
// дополнительный поток ( для запроса в кабинет )
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
ThreadsElements teTest = e.Argument as ThreadsElements; // берём объект для работы в тек. потоке ( см. синхронизацию переменных в потоках + google )
if (teTest.IsFirstTimeRun == true) // первый раз поток без задержки
{
teTest.IsFirstTimeRun = false;
}
else // зделать "задержку" что бы не дёргать базу слишком часто
{
if (teTest.iSleep > 0)
{
Thread.Sleep(teTest.iSleep); // исходя из выбранного значения в comboBox1
}
else
{
Thread.Sleep(300000); // по умолчанию 5 минут
}
}
if (teTest.s == null) // Нотес-сессия
{
try // тут может быть ошибка, пользователь отменяет воод пароля
{ teTest.s.Initialize(); }
catch (Exception err)
{
teTest.s = null;
teTest.IsConnected = false;
MessageBox.Show(err.Message);
// Включились в офф-лайн вещание
// nicnMain.Icon = Icon.FromHandle(((Bitmap)imageList1.Images[iOffLine]).GetHicon());
nicnMain.Text = "Авто оповещение [Откл.]";
}
}
if (teTest.s == null) { nicnMain.ShowBalloonTip(300, "Ошибка", "Подключение потеряно", ToolTipIcon.Info); }
else
{
try
{
Domino.NotesDatabase ndbTest;
Domino.NotesDocument ndTmp;
Domino.NotesDocumentCollection ndColl;
Domino.NotesView nvTest;
NotesMemos nmTmp;
int iNew = 0;
ndbTest = teTest.s.GetDatabase(teTest.ServerName, teTest.DBName, false); // Подключаемся к базе
nvTest = ndbTest.GetView(teTest.ViewName); // берём нужный "вид"
ndColl = nvTest.GetAllDocumentsByKey(nsSession.UserName, true); // берём документы по ключу
ndTmp = ndColl.GetFirstDocument(); // берём первый документ
while (ndTmp != null)
{
if (teTest.HasURL(ndTmp.NotesURL.ToString())==false) // а был ли такой документ в нашей коллекции, если небыл то...
{
iNew++;
nmTmp = new NotesMemos(); // доп. клас с описанием отдельно нотес-документа, см.ниже
nmTmp.sNotesURL = ndTmp.NotesURL.ToString(); // Нотес-урл для открытия из таблицы
nmTmp.sCreatedDate = ndTmp.Created.ToString(); // Дата создания документа
nmTmp.sTitle = ndTmp.GetFirstItem("Subject").Text.ToString(); // тема из документ
// тут можете собрать сколько угодно информации из документа, насколько хватит фантазии
teTest.AllInfo.Add(nmTmp); // вносим наш "документ" в list всех документов, см.ниже
}
ndTmp = ndColl.GetNextDocument(ndTmp);
}
}
catch (Exception nErr)
{
nicnMain.ShowBalloonTip(3000, "Подключение потеряно", nErr.Message, ToolTipIcon.Error);
}
e.Result = teTest; // определяем результат выполнения потока
}
}
// по окончанию потока, запустить его заново
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ThreadsElements teTest = e.Result as ThreadsElements;
int n = 0;
int c = 0;
if (teTest.IsConnected) // проверяем статус подключения
{
foreach ( NotesMemos sURL in teTest.AllInfo ) // для всех записей NotesMemos в list-коллекции
{
if (sURL.IsNew) // а новый ли это элемент
{
n = dtGridMain.Rows.Add(); // берём номер новой строки в таблице
dtGridMain.Rows[n].Cells[0].Value = (n+1).ToString(); // порядковый номер
dtGridMain.Rows[n].Cells[1].Value = sURL.sCreatedDate; // дата создания
dtGridMain.Rows[n].Cells[2].Value = sURL.sTitle; // Тема из документа
dtGridMain.Rows[n].Cells[3].Value = sURL.sNotesURL; // ссылка для открытия. Колонка скрыта
c++;
sURL.IsNew = false; // ставим поментку, что документ уже не новый
}
}
if (c > 0) // Есть новые сообщения
{
nicnMain.ShowBalloonTip(3000, "Внимание", "Новых сообщений: " + c.ToString() + " \n" + "Всего " + dtGridMain.Rows.Count.ToString(), ToolTipIcon.Info);
nicnMain.Text = "У Вас новых/всего сообщений " + c.ToString() + " / " + dtGridMain.Rows.Count.ToString() + "";
// Включились в офф-лайн вещание
// nicnMain.Icon = Icon.FromHandle(((Bitmap)imageList1.Images[iNewMsg]).GetHicon());
}
}
else
{
asyncObj = new ThreadsElements()
{
IsConnected = true,
ServerName = txtbxServer.Text,
DBName = txtbxDataBase.Text,
ViewName = txtbxView.Text,
iSleep = System.Convert.ToInt32(comboBox1.Text),
s = nsSession
};
}
backgroundWorker1.RunWorkerAsync(asyncObj);
}
И так, таблицу наполнили, запросы идут, уведомление из трея вылазит.
Осталось дорисовать открытия документа по двойному клику из ДатаГрид'a
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Двойной клик по записи в таблице</div></div><div class="sp-body"><div class="sp-content">
C++:
// Управление открытием ссылок
private void dtGridMain_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
int n = e.RowIndex;
try
{
System.Diagnostics.Process.Start(dtGridMain.Rows[n].Cells["NURL"].Value.ToString()); // открываем notesUrl
dtGridMain.ClearSelection(); // убираем выделение в таблице
// при открытии свернуть в трей
if (chBxHideInTrayOnDblClick.Checked)
Hide();
if (rbDBlClickChColor.Checked) // просто поменять цвет при открытии из таблицы
{
for (int i = 0; i <= dtGridMain.Rows[e.RowIndex].Cells.Count; i++)
{
dtGridMain.Rows[e.RowIndex].Cells[i].Style.BackColor = Color.LightGray;
}
}
else // удалить строку при открытии из таблицы
{
nicnMain.Text = "У Вас всего сообщений " + dtGridMain.Rows.Count.ToString();
dtGridMain.Rows.Remove(dtGridMain.Rows[e.RowIndex]);
}
}
catch { }
}
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">При закрытии - свернуть в трей</div></div><div class="sp-body"><div class="sp-content">
C++:
// При закрытии свернуть в трей
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason != CloseReason.TaskManagerClosing &&
e.CloseReason != CloseReason.WindowsShutDown && mClose != true)
e.Cancel = true;
this.WindowState = FormWindowState.Minimized;
Hide();
}
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">Класс для работы в паралельном потоке</div></div><div class="sp-body"><div class="sp-content">
C++:
public class ThreadsElements
{
// Properties public
public Boolean IsConnected = false; // индикатор подключения
public Domino.NotesSession s = null; // обьект - сессия
public String ServerName = ""; // имя сервера
public String DBName = ""; // путь к базе
public String ViewName = ""; // имя предаствления
public Boolean IsFirstTimeRun = false; // запущенно первый раз
public List<NotesMemos> AllInfo = new List<NotesMemos>(); // List(список) NotesMemos(см.ниже) записей
public int iSleep // = 300000; // каждые пять минут // задержка
{
get
{
return iMlSSlep;
}
set
{
if (value > 0)
{
iMlSSlep = value * 1000 * 60; // минуты в миллисекунды ... приблизительно
}
else
{
iMlSSlep = 300000;
}
}
}
// Properties private
private int iMlSSlep = 300000;
// Functions and Methods
public Boolean HasURL ( String sURL ) // Проверка на наличии в коллекции найденного документа
{
if (AllInfo.Count > 0)
{
foreach (NotesMemos k in AllInfo)
{
if (k.sNotesURL.Equals(sURL)) return true;
}
}
return false;
}
}
<div class="sp-wrap"><div class="sp-head-wrap"><div class="sp-head folded clickable">NotesMemos - класс для описания нотес-документа</div></div><div class="sp-body"><div class="sp-content">
C++:
public class NotesMemos
{
public String sNotesURL = ""; // ссылка на документ
public String sCreatedDate = ""; // дата создания
public String sTitle = ""; // тема
public Boolean IsNew = true; // новенький
}