Notesminder-напоминалка О Новых Документах Для Lotus

Тема в разделе "Разработки форумчан", создана пользователем morpheus, 17 фев 2011.

  1. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    Доброго всем времени сутокпредлагаю Вашему вниманию простенькую, но для "моих" нужд необходимую программу.<p>ВНИМАНИЕ! Программа обновилась. Актуальная версия 1.0.0.7
    • теперь вы можете использовать программу для нескольких баз данных (XML-файл с настройками).
    • нет необходимости вносить изменения в ваши базы данных - программа может просто проходится по всем документам в виде
    • программа поддерживает @Formula (но НЕ в имени сервера/базы/вида, только в полях относящихся к нотес-документам)
    • добавились всплывающие уведомления (как в ICQ)ю Сообщения автоматически пропадают через 15 сек (можно настроить).
    • теперь вы можете сохранять изменения внесённые в закладках настроек.
    <p>Суть: Приблуда каждые ХХХ минут стучится в заранее указанную базу данных Lotus Notes Domino и проверяет наличие для текущего пользователя "новых" документов. Если появилось что-то новое, об этом сообщается "облачком" из системного трея(см. скрины). Новое сообщение
    inComing.png
    Это, фактически, аналог встроенного minder'а в клиент Notes, с той лишь разницей, что мониторить можно любую доступную базу, проделав для этого минимум дизайнерский ухищрений.<p>Архитектура: Приблуда работает используя domino.tlb версии 6.5.4 ( тестировалась на 7.0.2 и 8.5.х клиентах ), в себе не хранит никаких данных (кроме тех что отображены на экране) и использует текущие настройки пользователя (расположение ИД-файла, подключения к серверу - используются нотес-имена). <p>Требования:Инсталлированный (или зарегистрированный как СОМ-Сервер в реестре Windows ) клиент Нотес, FrameWork 3.5 и выше, предоставление доступа для самого приложения (не всегда). Представление в целевой БД с первой сортировочной колонкой = имя текущего пользователя а также целевые документы должны иметь поле Subject - на основании которого и вычисляется что показать в списке<p>Начало работы и настройка: Для начала работы необходимо разархивировать скачанный архив в отдельную папку и запустить приложение. В закладках "Настройка" указать имя сервера Domino/XXX, путь к базе данных folder\dbname.nsf, а также имя представления MySuperView. С остальным думаю разберётесь.<p>Настройки теперь расширились. Из интерфейса программы можно поменять лишь некоторые параметры, а именно:
    • выводить всплывающие уведомления
    • Время "жизни" всплывающего уведомления
    • сворачивать в трей при открытии уведомления в лотус
    Для работы с базами данных используется отдельный XML файл - "dbConfig.xml". В котором, в тегах "DB" указываются подробные настройки как работать с базой: <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">[table] [tr] <th>TAG</th> <th>Поддерживаются @формулы</th> <th>Описание</th> <th>Пример</th> [/tr] [tr] server нет Сервер баз данных. имя из лотуса.MyServer/ORG [/tr] [tr] dbpath нет путь к базе данных.FOLDER\itProjects.nsf [/tr] [tr] nview нет наименование вида/папкиvMyView [/tr] [tr] key ДА Ключ для отработки ф-ции GetAllDocumentsByKey. В случае если пусто - обработаются все документы в виде. @UserName [/tr][tr] urlfield ДА Наименование поля/формула в котором содержится ссылка на документ. В случае если пусто - открывается документ-уведомление. ParentDocumentURL_FIELD [/tr][tr] interval НЕТ Периодичность обращений к базе даных. размерность минуты 1 [/tr][tr] field_subject ДА Наименование поля/формула в котором содержится тема документа Subject [/tr][tr] field_sender ДА Наименование поля/формула в котором содержится отправитель документа From [/tr][tr] field_date ДА Наименование поля/формула в котором содержится дата документа. Не работает @Created [/tr][tr] field_name_to_change ДА Наименование поля/формула в которое вносятся изменения в документе уведомлении. Н.: информация о том что документ прочитан readStatus [/tr][tr] field_value_to_change ДА Значения поля/формула которое вносится в документ уведомление. 1 [/tr] [/table]<p>ВНИМАНИЕ: При написании @Formula будьте внимательны - XML имеет некоторую специфику работы с символам - ковычки, апостроф, тильда, символ ограничивающий теги и т.д. - описываются ТАК. Пример правильной записи динамической формулы @middle есть в архиве!<p>Ввод пароля ( средствами нотес, приблуда об этом вообще ничего "не знает" )
    Password.JPG
    Пасмотрим что тут у нас
    Messages.JPG
    Сама программка<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">З.Ы. В принципе ничего не мешает доделать опцию запоминания пароля, но я специально не делал этого, т.к. не располагаю достаточными знаниями в криптовании, и не уверен что смогу достойно защитить пароли ( пока что ).
     

    Вложения:

    • Tray.JPG
      Tray.JPG
      Размер файла:
      10,2 КБ
      Просмотров:
      768
  2. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    Вот, добрался.

    Попробую пошагово показать как я реализовывал данную программку.

    1. Создаём Windows Forms Application.
    2. Добавить библиотеку типов - domino.tlb. Для этого в меню Project\Add Reference где в проводнике и находим нужную библиотеку (ищите в месте установке клиента Lotus Notes). Visual Studio сам создаст "обёртку" к данной библиотеке и Вы сможете использовать "родные" для Вас notes-классы.
    3. Я для себя определил следующюю схему:объявляю глобальную переменную нотес-сессии(Domino.NotesSession nsSession), по желанию пользователя инициирую, а сбор данных веду в отдельном потоке.
    4. Для сбора данных в отдельном потоке использую отдельный самописный клас(ThreadsElements) который "асинхронно" используется в компоненте BackgroundWorker
    5. Собранные данные скидываю в компоненту DataGrid, а при необходимости вывожу всплывающее сообщение из системного трея ( компонента NotifyIcon ).

    Листинг на 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; // новенький
    }
     
  3. shproteg

    shproteg Гость

    А реально чтобы данная программа работала без установленного клиента Lotus Notes?
     
  4. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    shproteg
    не думаю, хотя когда то видел тему про "урезаного клиента" ... но не понимаю зачем это, без лотуса сама программка теряет всякий смысл
     
  5. shproteg

    shproteg Гость

    Смысл в том что есть еще "тонкие" вэб клиенты а оповещалки у них нет вот и хотелось бы "независимую" оповещалку найти
     
  6. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    shproteg
    если веб - клиенты то оповещалку просто разместить на веб-морде - тут вам ajax в помощь
     
  7. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    А можно ли выводить пароль не в COM-окне, а в OLE, как это делает сам клиент? Задача в том, чтобы выбрать нужный Location, а не пользовать данные из notes.ini последнего пользователя, заходившего в клиент.

    И вообще бомба была бы, если на указанное количество секунд выводить не просто уведомление "новых столько-то", а выводить перечень уведомлений, и при щелчке на любом открывать сам док в Лотусе (OLE-то ведь инициализирован при вводе пароля).
    Ну и для писем выводить имя отправителя и тему.

    Ещё дать возможность натравливать на несколько баз, а во всплывающем окошке выводить:
     
  8. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    - это наврят


    - думаю список Location както можно будет выдернуть

    можно

    - думаю тоже можно будет допилить
     
  9. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    ВНИМАНИЕ программа обновилась
     
  10. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
    Пытаюсь скачать и не могу, - последний NOD говорит, что "Потенциальная угроза" и не даёт. Опция "Отключить защиту от вирусных и шпионских программ" не помогает. Как-нибудь можно обойти (вырубить антивирус не могу - политики)? Можно передать файл на проверку туда, тогда они через пару дней исключат из списка угроз.
     
  11. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    VladSh
    туда - это куда?
     
  12. VladSh

    VladSh начинающий
    Lotus team

    Регистрация:
    11 дек 2009
    Сообщения:
    1.251
    Симпатии:
    2
  13. seoman2

    seoman2 Lotus team
    Lotus team

    Регистрация:
    17 фев 2010
    Сообщения:
    435
    Симпатии:
    0
    А как сделать, чтобы не надо было вводить пароль от ид при запуске?
    Программк не всегда выходит из трея.
     
  14. morpheus

    morpheus скриптописец

    Регистрация:
    7 авг 2006
    Сообщения:
    3.927
    Симпатии:
    0
    Я не смогу обеспечить "безопасноть" такого пароля, т.к. прийдёться хранить его в открытом виде (ну или в очень "легко" зашифрованном).

    Есть такое, "отловить" не получается ((
     

Поделиться этой страницей