Сегодня 24 января, пятница ГлавнаяНовостиО проектеЛичный кабинетПомощьКонтакты Сделать стартовойКарта сайтаНаписать администрации
Поиск по сайту
 
Ваше мнение
Какой рейтинг вас больше интересует?
 
 
 
 
 
Проголосовало: 7278
Кнопка
BlogRider.ru - Каталог блогов Рунета
получить код
Dmitriy
Dmitriy
Голосов: 0
Адрес блога: http://demiware.ru
Добавлен: 2014-01-14 07:07:16
 

Форма-фильтр на фронте в Symfony2

2014-01-10 04:00:00 (читать в оригинале)

Форма-фильтр на фронте в Symfony2

В предыдущей версии Symfony 1.4 фрэймворк для нас любезно генерил форм-фильтры. В Symfony 2.*, увы, такой халявы не стало. Конечно можно использовать какие-нибудь сторонние бандлы, но этот путь, лично для меня не показался достаточно простым и прозрачным. Ну чтож, будем сами решать данную проблему. Не все так страшно как оказалось.

Фильтр нам надо сделать чтобы данные из таблицы фильтровались по полю с датой - по периоду от начальной даты до конечной и еще по одному полю типа Entity. В моем случае таблица представляет из себя загрузки трансформаторов подстанций, там есть поле с датой и соответственно будем фильтровать по производственному объединению(ПО), которые имеют свои подстанции с этими трансформаторами.

 

Итак фильтруемый entity выглядит примерно так:

 
class TransPeregruz
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    
    /**
     * @var transformator
     *
     * @ORM\ManyToOne(targetEntity="transformator", inversedBy="peregruzs")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="transformator_id", referencedColumnName="id", onDelete="SET NULL", nullable=true)
     * })
     */
    private $transformator;

....................................................

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="PikDt", type="datetime")
     */
    private $pikDt;
....................................................
}

В нем нас будут интересовать свойства transformator(трансформатор) и pikDt(дата пикового значение нагрузки). Форму фильтр для этого дела, правильней было бы сделать отдельным файлом в папочке Form моего бандла, но я, как человек иногда ленивый, делаю ее прямо в контроллере фильтруемой сущности "на лету". Выношу создание формы в функцию CreateFilterForm, также привожу вашему вниманию код

class TransPeregruzController extends Controller
{
    private function CreateFilterForm($po, $dt_from = NULL, $dt_to = NULL)
    {
        if (!isset($dt_from) || !isset($dt_to))
        {
            $dt1 = new \DateTime();
            $dt1->sub(new \DateInterval('P1D'));
            $dt2 = new \DateTime();
            $dt_to = $dt2;
            $dt_from = $dt1;
        }    
        $data = array('PO' => $po,'date_from' => $dt_from,'date_to' => $dt_to);
        $pos = $this->getDoctrine()->getManager()->getRepository('BpBundle:Po')->findAll();
        $poChoices = array();
        foreach ($pos as $po_obj) {
            $key = $po_obj->getId();
            $value = $po_obj->getShortName();
            $poChoices[$key] = $value;
        }
        $fb = $this->createFormBuilder($data, array('csrf_protection' => false));
        $formFilter =
            $fb
            ->add('PO','choice', array(
                    'label'  => 'ПО',
                    'choices' => $poChoices,
                    'empty_value' => 'Все ПО', 'required' => false, 
                    'attr' => array('class' => 'input-small'))  
                  )
            ->add('date_from','datetime',array('label' => 'Период начиная с:', 'widget' => 'single_text',
                'format' => 'dd.MM.yyyy HH:mm',
                'attr' => array('class' => 'input-medium dtp')))
            ->add('date_to','datetime',array('label' => 'до:', 'widget' => 'single_text',
                'format' => 'dd.MM.yyyy HH:mm',
                'attr' => array('class' => 'input-medium dtp')))
            ->getForm();
        return $formFilter;
    }
    
...............................................

    public function indexAction()
    {
        $session = $this->getRequest()->getSession();
        $po = $session->get('PO'); 
        $dt1 = new \DateTime();
        $dt1->sub(new \DateInterval('P1D'));
        $dt2 = new \DateTime();
        $dt_to = $session->get('date_to',$dt2);
        $dt_from = $session->get('date_from',$dt1);
           
        $em = $this->getDoctrine()->getManager();
        $repository = $em->getRepository('BpBundle:TransPeregruz');
        $queryBuilder = $repository->createQueryBuilder('p')
            ->leftJoin('p.transformator', 'ts')    
            ->leftJoin('ts.podst', 'ps')
            ->leftJoin('ps.groupPodst', 'gps')
            ->leftJoin('gps.po', 'po')
            ->where('p.pikDt>=:dtfrom AND p.pikDt<=:dtto')
            ->orderBy('p.pikDt','DESC')
            ->setParameter('dtfrom', $dt_from)
            ->setParameter('dtto', $dt_to);    
            if (isset($po))
                $queryBuilder->andWhere('po.id = :po')
                ->setParameter('po', $po);    

        $entities = $queryBuilder->getQuery()->getResult();
        $formFilter = $this->CreateFilterForm($po, $dt_from, $dt_to);
        
        return array(
            'entities' => $entities,
            'form' => $formFilter->createView(),
        );
    }

    public function filterAction(Request $request)
    {
        $session = $request->getSession();
        $po = $session->get('PO'); 
        $formFilter = $this->CreateFilterForm($po, $dt_from, $dt_to);
        $formFilter->handleRequest($request);
        
        if ($formFilter->isValid()) {
            $data = $formFilter->getData();
            if (isset($data['PO']))
                $session->set('PO', $data['PO']);
            else {
                $session->remove('PO');
            }        
            $session->set('date_from', $data['date_from']);
            $session->set('date_to', $data['date_to']);
            return $this->redirect($this->generateUrl('peregruz'));
        }
        
        return $this->render('BpBundle:TransPeregruz:filter.html.twig', array(
            'form'    => $formFilter->createView(),
        ));
    }

..............................................................................................

Давайте проясним сразу некоторые моменты. Не буду разбирать построчно, думаю вы способны на это сами, отделаюсь общими словами. По умолчанию date_to и date_from устанавливаются в текущую дату минус сутки, то есть если пользователь еще ничего не выбрал мы ему подставляем в фильтр период за последние 24 часа. спасибо DateTime классу, который позволяет это легко сделать. Другой виджет фильтра заполняем сущностями из таблицы ПО. После того как пользователь определил свой выбор данных для виджетов фильтра, сохраняем их в сессии, чтобы при регенерации страницы была возможность их снова показать.

Роуты (..\Resources\config\routing.yml):

..............
peregruz:
    pattern:  /peregruz
    defaults: { _controller: BpBundle:TransPeregruz:index }
    methods:  [GET|POST]
        
peregruz_filter:
    pattern:  /peregruz_filter
    defaults: { _controller: BpBundle:TransPeregruz:filter }
    requirements:
        _method:  POST 
..............

Twig-шаблон filter.html.twig:

{% block body -%}
    <form action="" method="post">
        {{ form_errors(form) }}
                <div class="control-group">
                    {{ form_label(form.PO) }}
                    {{ form_errors(form.PO) }}
                    {{ form_widget(form.PO) }}
                </div>
                <div class="control-group">
                    {{ form_label(form.date_from) }}
                    {{ form_errors(form.date_from) }}
                    {{ form_widget(form.date_from) }}
                </div>
                <div class="control-group">
                    {{ form_label(form.date_to) }}
                    {{ form_errors(form.date_to) }}
                    {{ form_widget(form.date_to) }}
                </div>
        <p>
            <br> <button class="btn btn-primary" type="submit">Фильтровать</button>
        </p>
    </form>
{% endblock %}

Twig-шаблон index.html.twig (частично):

.........................

{% block sidebar %}
    <div class="container-fluid">
      <div btn-group-vertical">  
        <a href="http://demiware.ru/{{ path('bp_mod_zt') }}" class="btn btn-info">
            <i class="icon-home"></i> Модуль загрузки трансформаторов
        </a>
        
      </div> 
      <ul class="nav nav-pills nav-stacked">
          <li>
          <a href="http://demiware.ru/{{ path('prognoz') }}">Отчет по превышению прогнозов загрузки без ТУ</a>
          <li class="active">
          <a href="http://demiware.ru/{{ path('peregruz') }}">Отчет по превышению загрузки трансформаторов</a>
          </li>
      </ul>      
      <hr>      
      <div id="filter"> 
         <form action="{{ path('peregruz_filter') }}" method="post" {{ form_enctype(form) }} class="form-horizontal">
         {% include 'BpBundle:TransPeregruz:filter.html.twig' with { 'form': form } %}
         </form>
      </div> 
    </div> 
        
{% endblock %}

.........................

{% block javascripts %} 

<script type="text/javascript">
    
    $( '.dtp' ).datetimepicker({showButtonPanel: true});

</script>

{% endblock %}

Обратим внимание, что для виджетов dat_to и date_from при создании формы в контроллере, я задал класс dtp, а здесь мы на него навешиваем красивый JQuery контрол DateTimePicker.



Прикручиваем динамический аккордеон от Twitter Bootstrap в проекте Symfony

2014-01-06 19:36:25 (читать в оригинале)

Прикручиваем динамический аккордеон от Twitter Bootstrap в проекте Symfony

Пример привожу из своего проекта - некий WEB-сервис для энергетической компании региона. Имеем Следующие объекты(entity в терминологии Symfony2) 

  • производственные отделения (ПО)
  • относящиеся к ПО группы электроподстанций

Отношения таблиц один ко многим, ну это понятно и все описано в схеме проекта. Задача - сделать аккордеон в левой части страницы, на верхнем уровне которого должны быть ПО, а подпункты - группы подстанций. Если вам нужно больше иерархий ничто не мешает сделать это по аналогии.

Собственно основные события происходят по созданию сего аккордеона происходят в контроллере - entity подстанций в данном случае, но это не принципиально в каком. И в шаблоне, который у меня включаемый, потому-что общий для нескольких видов других шаблонов. По нажатию по ссылке на аккордеоне в контенте шаблона должна выводиться таблица с подстанциями. Загвоздка в том, что после обновления страницы надо воспроизвести аккордеон в том виде в каком он был развернут пользователем. Код из контроллера подстанций:

  ............
    protected $pos;
    protected $em;
    public function init() {
        $this->em = $this->getDoctrine()
                ->getManager();
        $this->pos = $this->em->getRepository('BpBundle:Po')
                ->findAll();
    }

    protected function getPo($po_id)
    {
        $em = $this->getDoctrine()
                    ->getManager();

        $po = $em->getRepository('BpBundle:Po')->find($po_id);

        if (!$po) {
            throw $this->createNotFoundException('Не найдено ПО.');
        }

        return $po;
    }
    
    protected function getGP($gp_id)
    {
        $em = $this->getDoctrine()
                    ->getManager();

        $gp = $em->getRepository('BpBundle:GroupsPodst')->find($gp_id);

        if (!$gp) {
            throw $this->createNotFoundException('Не найдено ПО.');
        }

        return $gp;
    }
    ..........
    public function indexAction($po_id, $is_po)
    {
        $this->init();
//        $em = $this->getDoctrine()->getManager();
        $repository = $em->getRepository('BpBundle:Podst');
        if ($is_po) {
            $po = $this->getPo($po_id);
            $queryBuilder = $repository->createQueryBuilder('p')
            ->leftJoin('p.groupPodst', 'g')
            ->leftJoin('g.po', 'po') 
            ->where('po = :po')
            ->orderBy('p.name')        
            ->setParameter('po', $po);
            $podsts = $queryBuilder->getQuery()->getResult();
        } else {
            $gp = $this->getGP($po_id);
            $po = $this->getPo($gp->getPo()->getId());
            $podsts = $repository->findBy(array('groupPodst' => $gp),array('name' => 'ASC'));
        }
        ........
        return $this->render('BpBundle:Podst:index.html.twig', array(
                    'podsts' => $podsts, 'po' => $is_po?$po:$gp, 
                    'pos' => $this->pos, 'is_po' => $is_po, 'dostup' => $ok,
                ));
    }
 ............

Смотрим функцию indexAction($po_id, $is_po), в которой формируются данные для шаблона. Тут $is_po - сигнализирует нажата ли ссылка ПО - тогда надо выводить все подстанции ПО, если нет, то подстанции относящиеся к выбранной группе. Но главное, что важно для нашего аккордеона - мы для него передали в шаблон массив объектов $pos - то есть все ПО.

А теперь рассмотрим шаблон, который у нас сделан на twig.

 
{% extends '::base.html.twig' %}
{% block sidebar %}
            <div class="bs-docs-sidebar">
              {# Po accordion #} 
              <div class="accordion" id="accordion1">
                {% for po in pos %}
                      <div class="accordion-group">
                        <div class="accordion-heading">
                          <a class="accordion-toggle btn btn-inverse" data-toggle="collapse" data-parent="#accordion1" href="http://demiware.ru/#collapse{{po.id}}">
                            {{po.name}} <i class=" icon-chevron-down icon-white"></i>
                          </a>
                        </div>
                              
                        <div id="collapse{{po.id}}" class="accordion-body collapse">
                          <div class="accordion-inner">
                            <div class="btn-group btn-group-vertical" data_toggle="radio-button">
                            <a id="b1_{{po.id}}" type="button" class="inv sbar btn btn-inverse" href="http://demiware.ru/{{ path('po_edit', { 'id': po.id }) }}">
                             Редактировать ПО <i class="icon-chevron-right icon-white"></i> </a>    
                            <a id="b2_{{po.id}}" type="button" class="inv sbar btn btn-inverse" href="http://demiware.ru/{{ path('podst', { 'po_id': po.id, 'is_po': 1 }) }}">
                             Все подстанции <i class="icon-chevron-right icon-white"></i> </a>  
                             </div>
                              <br>
                              
                              <div class="btn-group btn-group-vertical" data_toggle="radio-button">
                              {% for gp in po.gps %}    
                               <a id="b_{{gp.id}}" class="sbar accordion-toggle btn btn-small btn-primary" href="http://demiware.ru/{{ path('podst', { 'po_id': gp.id, 'is_po': 0 }) }}" >
                                  {{gp.name}} <i class="icon-chevron-right icon-white"></i>
                               </a>
                               {% else %}
                                <p>в {{po.name}} нет групп подстанций...</p>       
                               {% endfor %}       
                              </div> 
                            
                          </div>
                        </div>
                      </div>
        {% else %}
            <p>таблица Po пуста...</p>
        {% endfor %}  
              </div>
            </div>

{% endblock %}

{% block javascripts %} 

<script src="http://demiware.ru/{{ asset('js/bp.js') }}"></script>

{% endblock %}

Первой строкой он расширяется базовым шаблоном, в котором хранятся все подключения основных CSS, главное меню, шапка, подвал и прочее. В свою очередь этот шаблон сам расширяет уже шаблоны в которых выводятся таблицы с подстанциями и другие некоторые. Цикл вывода содержимого аккордеона разбирать не будем, он понятен. Обратим внимание, что за скрипт подключен внизу в файле bp.js, в этом файле есть код касающийся данного аккордеона. Именно этот ява-скрипт позволяет сохранять его состояние в куки и выделять активный пункт.

$(document).ready(function() {
..........................
    //////////////////////Играем на аккордеоне//////////////////////////////////
    var last=$.cookie('activeAccordionGroup');
    var pth=window.location.pathname; //alert(pth);
    if (last!==null && !(pth=="/" || pth=="/app_dev.php/")) {
        //remove default collapse settings
        $("#accordion1 .collapse").removeClass('in');
        //show the last visible group
        $("#"+last).collapse("show");
    }
    
    last=$.cookie('activeButton');
    
    if (last!==null) {
        //$("a.sbar").removeClass('btn-info');
        $("#"+last).removeClass('btn-inverse btn-primary');
        $("#"+last).addClass('btn-info');
    }
    
    //when a group is shown, save it as the active accordion group
    $("#accordion1 .collapse").on('show', function() {
        var active=$(this).attr('id');
        $.cookie('activeAccordionGroup', active, {
            expires: 7, 
            path: '/'
        });
    });
    
    $("a.sbar").on('click', function() {
        var last=$.cookie('activeButton');
        if (last!==null) {
            $("#"+last).removeClass('btn-info'); 
            if ($("#"+last).hasClass('inv')) { 
                $("#"+last).addClass('btn-inverse');
            } 
            else { 
                $("#"+last).addClass('btn-primary');   
            } 
        }
        var active=$(this).attr('id');
        $(this).removeClass('btn-inverse btn-primary');
        $(this).addClass('btn-info');
        $.cookie('activeButton', active, {
            expires: 7, 
            path: '/'
        });
    });
    
........
});
 


Секреты создания служб Windows. Часть 2. Детали.

2014-01-05 19:32:42 (читать в оригинале)

Продолжим рассматривать программирование служб, начатое ранее в первой части. Рассматривая детали, мы коснемся таких моментов как работу с объектами синхронизации и с множеством потоков. Покажем как создать произвольное количество потоков и, запустив их, не потерять над ними контроль. 

Когда-то очень давно я нашел в сети модуль класса - оболочку для объекта синхронизации WaitableTimer, автор Алексей Вуколов. Спасибо большое Алексею, модуль небольшой и очень простой, но мне он не раз пригодился. Привожу здесь его код.

unit wtimer;

{ **** UBPFD *********** by delphibase.endimus.com **** 
 >> Класс-оболочка для объекта синхронизации WaitableTimer. 
 
 Класс представляет собой оболочку для объекта синхронизации WaitableTimer, 
 существующего в операционных системах, основанных на ядре WinNT. 
 
 Методы. 
 -------------- 
 Start - запуск таймера. 
 
 Stop - остановка таймера. 
 
 Wait - ожидает срабатывания таймера заданное количество миллисекунд и 
 возвращает результат ожидания. 
 
 Свойства. 
 -------------- 
 Time : TDateTime - дата/время когда должен сработать таймер. 
 
 Period : integer - Период срабатывания таймера. Если значение равно 0, то 
 таймер срабатывает один раз, если же значение отлично от нуля, таймер будет 
 срабатывать периодически с заданным интервалом, первое срабытывание произойдет 
 в момент, заданный свойством Time. 
 
 LongTime : int64 - альтернативный способ задания времени срабатывания. Время 
 задается в формате UTC. 
 
 Handle : THandle (только чтение) - хендл обекта синхронизации. 
 
 LastError : integer (только чтение) - В случае если метод Wait возвращает 
 wrError, это свойство содержит значение, возвращаемое функцией GetLastError. 
 
 Зависимости: Windows, SysUtils, SyncObjs 
 Автор:  vuk 
 Copyright: Алексей Вуколов 
 Дата:  25 апреля 2002 г. 
 ***************************************************** } 
 
 interface 
 
 uses 
 Windows, SysUtils, SyncObjs; 
 
 type
 
 TWaitableTimer = class(TSynchroObject) 
 protected 
  FHandle: THandle; 
  FPeriod: longint; 
  FDueTime: TDateTime; 
  FLastError: Integer; 
  FLongTime: int64; 
 public 
 
  constructor Create(ManualReset: boolean; 
  TimerAttributes: PSecurityAttributes; const Name: string); 
  destructor Destroy; override; 
 
  procedure Start; 
  procedure Stop; 
  function Wait(Timeout: Cardinal): TWaitResult;
 
  property Handle: THandle read FHandle; 
  property LastError: integer read FLastError; 
  property Period: integer read FPeriod write FPeriod; 
  property Time: TDateTime read FDueTime write FDueTime; 
  property LongTime: int64 read FLongTime write FLongTime; 
 
 end; 
 
 implementation 
 
 { TWaitableTimer } 
 
 constructor TWaitableTimer.Create(ManualReset: boolean; 
 TimerAttributes: PSecurityAttributes; const Name: string); 
 var 
 pName: PChar; 
 begin 
 inherited Create; 
 if Name = '' then 
  pName := nil 
 else 
  pName := PChar(Name); 
 FHandle := CreateWaitableTimer(TimerAttributes, ManualReset, pName); 
 end; 
 
 destructor TWaitableTimer.Destroy; 
 begin 
 CloseHandle(FHandle); 
 inherited Destroy; 
 end; 
 
 procedure TWaitableTimer.Start; 
 var 
 SysTime: TSystemTime; 
 LocalTime, UTCTime: FileTime; 
 Value: int64 absolute UTCTime; 
 
 begin 
 if FLongTime = 0 then 
 begin 
  DateTimeToSystemTime(FDueTime, SysTime); 
  SystemTimeToFileTime(SysTime, LocalTime); 
  LocalFileTimeToFileTime(LocalTime, UTCTime); 
 end 
 else 
  Value := FLongTime; 
 SetWaitableTimer(FHandle, Value, FPeriod, nil, nil, false); 
 end; 
 
 procedure TWaitableTimer.Stop; 
 begin 
 CancelWaitableTimer(FHandle); 
 end; 
 
 function TWaitableTimer.Wait(Timeout: Cardinal): TWaitResult;
 begin 
 case WaitForSingleObjectEx(Handle, Timeout, BOOL(1)) of 
  WAIT_ABANDONED: Result := wrAbandoned; 
  WAIT_OBJECT_0: Result := wrSignaled; 
  WAIT_TIMEOUT: Result := wrTimeout; 
  WAIT_FAILED: 
  begin 
  Result := wrError; 
  FLastError := GetLastError; 
  end; 
 else 
  Result := wrError; 
 end; 
 end; 
 
 end.

 // Пример создания таймера, который срабатывает по алгоритму "завтра в это же 
 // время и далее с интервалом в одну минуту". 
 
// var
// Timer: TWaitableTimer;
// begin
// Timer := TWaitableTimer.Create(false, nil, '');
// Timer.Time := Now + 1; //завтра в это же время
// Timer.Period := 60 * 1000; //Интервал в 1 минуту
// Timer.Start; //запуск таймера
// end;

По сути здесь зашит функционал будильника. TWaitableTimer нам пригодиться и сейчас. Рассмотрим тело основного потока нашего примера TSparkyThread, то есть метод Execute 

procedure TSparkyThread.Execute;
var
  yy, mn, dd, hh, mm, ss, ms: Word;
  SUCCESS: HResult;
  CurDT: TDateTime;
begin
    CurDT := Now;
    DecodeDateTime(CurDT, yy, mn, dd, hh, mm, ss, ms);
    With ZTService do
    begin
      TimeRun := EncodeDateTime(yy, mn, dd, h1, m1, s1, 0);
      if CurDT > TimeRun then
      begin
        TimeRun := IncHour(TimeRun, period);//IncDay(TimeRun);
      end;
      WriteLog('Таймер установлен на ' + DateTimeToStr(TimeRun));
      Timer := TWaitableTimer.Create(False, nil, '');
      try
        Timer.Time := TimeRun; // время србатывания
        Timer.period := period*60*60*1000;
        Timer.Start; // запуск таймера
        while not Terminated do
          if Timer.Wait(INFINITE) <> wrError then
          begin
            if not Terminated then
            begin
              WriteLog('Таймер сработал');
              SUCCESS := CoInitialize(nil);
              try
                ExeWork;
              finally
                case SUCCESS of
                  S_OK, S_FALSE:
                    CoUninitialize;
                end;
                WriteLog('Загрузка завершена');
                TimeRun := IncHour(TimeRun, period);
                WriteLog('Таймер установлен на ' + DateTimeToStr(TimeRun));
              end;
            end;
          end else
            WriteLog('Ошибка таймера');
      finally
        Timer.Free;
      end;
    end;
end;

Что мы здесь видим... Для начала устанавливается время срабатывания таймера, часы минуты и секунды мы берем из ранее считанного INI-файла и прибавляем к текущей дате. Также из INI взят период в часах через который срабатывание повторяется. Важен вызов CoInitialize, поскольку эта служба планирует работать с какой-то базой данных через ADO, без этой функции ничего не выйдет. Ну и, конечно, логирование, куда без него. А основная работа будет выполняться внутри ExeWork.

procedure TZTService.ExeWork;
var
  fld: TField;
  Handles: array of THandle;
  Threads: array of TPOThread;
  I,N: Integer;
begin
  ADOConnection1.Connected := True;
  try
    if ADOConnection1.Connected then
    begin
      WriteLog('Соединение с SQL-сервером установлено');
      PoTable.Open;
      PoTable.First;
      N:= PoTable.RecordCount;
      SetLength(Handles, N);
      SetLength(Threads, N);
      I:=0;
      while not PoTable.Eof do
      begin
        po := Trim(PoTable.FieldByName('short_name').AsString);
        WriteLog('Начало загрузки данных по ' + po);
        fld := PoTable.FieldByName('oik');
        if not (fld.IsNull or (Trim(fld.AsString) = '')) then
           oik := Trim(PoTable.FieldByName('oik').AsString)
        else  oik := '';

          Threads[I]:= TPOThread.Create(PoTable.FieldByName('id').AsInteger, oik);
          Handles[I] := Threads[I].Handle;

        Inc(I);
        PoTable.Next;
      end;
      // Wait until threads terminate
      // This may take up to ArrLen - 1 seconds
      WaitForMultipleObjects(N, @Handles, True, INFINITE);

      // Destroy thread instances
      for I := 0 to N - 1 do
        Threads[I].Free;
    end
    else
    begin
      WriteLog('ОШИБКА! Соединение с SQL-сервером НЕ УСТАНОВЛЕНО!');
    end;
  finally
    if ADOConnection1.Connected then
      ADOConnection1.Close;
  end;
end;

Как можно отметить в выше приведенном коде происходит соединение с неким SQL-сервером, далее чтение записей с таблицы (поясню, что компоненты таблицы и коннекшена к БД, в данном случае, лежат на форме службы). На основании количества подходящих записей из таблицы создается нужное количество потоков, хэндлеры и объекты которых запоминаем в двух массивах. После того как дождались выхода из функции WaitForMultipleObjects(N, @Handles, True, INFINITE) - а это значит все потоки завершили работу - удаляем их, освобождая память. То что происходит внутри каждого из этих потоков неважно, в моем случае это было чтение данных с серверов телемеханики, единственное что там надо быть уже более внимательным с синхронизацией, чтобы не допустить одновременного обращения к каким-либо общим объектам или переменным, но это уже другая история...



Упражнение Avatar

2014-01-01 20:41:27 (читать в оригинале)

Упражнение Avatar

Название Avatar никак не навеяно одноименным знаменитым блокбастером. Так эта задача была названа заказчиком, было это когда данного фильма не было даже в проекте, в начале нулевых. В то время я пытался продавать свои компоненты на Delphi, которые были предназначены для создания векторных редакторов.

Был сайт шароварный с форумом, где можно было скачать, купить и прочее. Дела шли не ахти или просто не шли, хотя несколько копий библиотеки с сырцами были куплены людьми с разных концов света. Однажды ко мне постучался некий американец, которого звали как человека родом с полуострова Индостан. Он хотел, чтоб я ему за некоторое вознаграждение сделал пару примеров - первый растровая графика (этот Avatar и есть), другой по векторной.

У него было нормальное ТЗ и набор ресурсов в виде картинок. Поскольку деньги у меня никогда не были лишними, а задание было достаточно простым, я согласился без особых раздумий. К сожалению денег я с того американо-индийского товарища так и не дождался, хотя сделал все как надо. Но работа была проделана не зря, примерно через года полтора на меня вышел другой чел, тоже со штатов и тоже с именем выходца с пакистано-индийских краев. Я был удивлен, но просил он меня сделать практически то же самое, но без аватара, а в части векторной графики - второй проект. Я взял с него предоплату и отправил сырцы второго задания предыдущего америко-индуса, клиент остался доволен. Такая вот история.

Но я отвлекся. Вернемся к нашему упражнению. Как мне объясняли заказчики, им все это нужно было для какого-то навороченного чата, где можно было бы брать себе аватар-картинку и в неком "виртуальном пространстве"(если это можно так назвать) общаться...

Функционал примера прост.

  • Выбираем себе картинку аватар вызвав соответствующую форму.
  • Вводим свой ник, он будет отображаться под аватаром.
  • Вводим сообщения, которые появляются в "баллоне" около головы аватара, можно многострочные.
  • Если кликаем мышкой в окне, аватар "переезжает" в эту точку, здесь два варианта "заплыва" - по таймеру и в отдельном потоке (в потоке аватар бежит шустрее).

 



Game Over Free

2013-12-31 09:03:11 (читать в оригинале)

Game Over Free

Небольшое приложение, позволяющее запретить запуск процессов из составленного Вами списка, например, запретить какие-либо игры или "вредные", с Вашей точки зрения, программы...

Нежелательный процесс как только запуститься - тут же будет завершен.

 

Принцип следующий:

  • Добавляете названия процессов, которые вам не хочется чтобы запускались на данном компе
  • Жмете кнопу "Start monitoring" 
  • Если хотите чтобы запускалась после перезагрузки, включения ставим флаг "Add to avtorun"
  • Жмем "Hide Game Over" - программа прячется и видно ее только в списке процессов
  • Если надо восстановить ее на экран жмем Ctrl-Shift-F5

 



Страницы: 1 2 

 


Самый-самый блог
Блогер ЖЖ все стерпит
ЖЖ все стерпит
по сумме баллов (758) в категории «Истории»


Загрузка...Загрузка...
BlogRider.ru не имеет отношения к публикуемым в записях блогов материалам. Все записи
взяты из открытых общедоступных источников и являются собственностью их авторов.