Страницы

Поиск по вопросам

суббота, 9 марта 2019 г.

QProcess - как получить вывод процесса до его завершения?

Есть приложение на Qt, включающее код такого вида :
QProcess proc; ... proc.setProgramm("Python34\\python"); proc.setArguments(QStringList{"hello.py"}); proc.setProcessChannelMode(QProcess::MergedChannels); connect(&proc, &QProcess::readyReadStandardOutput, []() { QString output = QString(proc.readAllStandardOutput()); ... }); proc.start();
Но readyReadStandardOutput отлавливается только после завершения процесса. В моём случае - требуется проделать определённые действия до завершения (по наличию в выводе определённого текста).
Как это реализовать?
upd. Более подробный код имеет вид :
class Window: public QMainWindow { ... private: QVector processes; void createProcesses(); void prepareProcess(QProcess* proc); }
void MainWindow::createProcesses() { for(int i=0;...) { processes.append(new QProcess()); prepareProcess(&processes[processes.size()-1]); } }
void MainWindow::prepareProcess(QProcess* proc) { proc->setProgramm("Python34\\python"); proc->setArguments(QStringList{"hello.py"}); proc->setProcessChannelMode(QProcess::MergedChannels); connect(&proc, &QProcess::readyReadStandardOutput, []() { QString output = QString(proc->readAllStandardOutput()); ... }); proc->start(); }
UPD. А в Qt ли вообще дело? Обнаружил следующее. При запуске, например ping /t ya.ru результат выводится. Но, с применением например, следующего питоноскрипта - нет
import time while True: print("Hello") time.sleep(1)


Ответ

Дело было не в приложении на Qt, а в буферизации вывода python-ом (https://stackoverflow.com/questions/230751/how-to-flush-output-of-python-print)
Лечится запуском с опцией -u

Как сделать выборку по дате из текстового поля

У меня таблица в формате (поле с типом text):
+------------------+ | data | +------------------+ | 23.06.2015 10:30 | | 24.06.2015 10:45 | | 26.06.2015 12:15 | | 02.07.2015 11:24 | +------------------+
Если ищу между датами, то находит неправильно. Только если ищу по одному месяцу, допустим по июню, если ищу с июня по июль, то ничего не выводит.
Такой запрос:
mysql_query("SELECT * FROM `selling_list` WHERE `data`>'22.06.2015' AND `data`<'03.07.2015'");
выдаст пустой результат. а такой:
mysql_query("SELECT * FROM `selling_list` WHERE `data`>'22.06.2015' AND `data`<'30.06.2015'");
выдаст 3 результата.
Почему так? Как правильно сделать выборку?


Ответ

если дата в строке указана в понятном серверу mysql формате, то диапазон можно указывать, например, так:
select ... where data between "2015-06-20" and "2015-07-03";
но у вас ситуация осложняется тем, что в базе данных содержится, во-первых, строка, а не дата, а во-вторых в «непонятном» серверу формате.
значит, надо преобразовывать строку при выборке с помощью функции str_to_date, указав вторым параметром подходящий шаблон
пример (в выборку попала дата за 23 июня и не попала за 2 июля потому, что если не указано время, то оно берётся как «полночь» — 0:00:00):
SQL Fiddle
MySQL 5.6 Schema Setup
create table t (data text);
insert into t values ("23.06.2015 10:30"), ("24.06.2015 10:45"), ("26.06.2015 12:15"), ("02.07.2015 11:24");
Query 1
select * from t where str_to_date(data, "%d.%m.%Y %k:%i:%s") between "2015-06-23" and "2015-07-02"
Results
| data | |------------------| | 23.06.2015 10:30 | | 24.06.2015 10:45 | | 26.06.2015 12:15 |

Аналог функции system из C++ для Rust

Есть ли в Rust аналог функции system из C++? Или что-то подобное, позволяющее посылать команды командному процессору Linux


Ответ

use std::process::Command;
let status = Command::new("ls").status().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
println!("process exited with: {}", status);
Документация std::process::Command
Однако в винде это не сработает. Там можно так:
use std::process::Command; let status = Command::new("cmd.exe").arg("/c").arg("dir").status().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

Как сохранить таблицу из DataGridView в БД (MS Access)?

Каким образом можно сохранить таблицу, изменённую в DataGridView, в БД?
Отображаю таблицы так:
public void refreshTable() { string conStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db.mdb"; OleDbConnection connection = new OleDbConnection(conStr); OleDbDataAdapter adapter = new OleDbDataAdapter();
connection.Open(); OleDbCommand command = new OleDbCommand("SELECT * FROM " + comboBox1.Text, connection); //в comboBox1 название таблицы
connection.Close();
adapter.SelectCommand = command; DataSet dataSet = new DataSet(); adapter.Fill(dataSet); dataGridView1.DataSource = dataSet.Tables[0]; adapter.Update(dataSet); }
Везде ссылаются на эту статью: http://tech.pro/tutorial/664/csharp-tutorial-binding-a-datagridview-to-a-database
Но я никак не пойму. Взял код оттуда:
private void saveButton_Click(object sender, EventArgs e) { string conStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db.mdb"; OleDbConnection connection = new OleDbConnection(conStr); OleDbDataAdapter adapter = new OleDbDataAdapter("SELECT * FROM " + comboBox1.Text, connection);
BindingSource bSource = new BindingSource(); DataTable dTable = new DataTable();
adapter.Fill(dTable); bSource.DataSource = dTable; dataGridView1.DataSource = bSource;
adapter.Update(dTable); }
Но он только выводит исходную таблицу из БД (что довольно очевидно). Что мне следует изменить в этом методе?
Примерно так выглядит добавление строк в таблицу:

После вызова метода через нажатие кнопки, 3 новые строки исчезают и, соответственно, не попадают в БД.
Обновление: кроме того, если я ввожу что-нибудь недопустимое (буквы в качестве ключа или даты), то получаю исключение, при вызове saveButton_Click.
Обновление: добавил в начале метода записи в БД:
connection.Open(); adapter.SelectCommand = new OleDbCommand("SELECT * FROM dataGridView1", connection); adapter.UpdateCommand = cBuilder.GetUpdateCommand(true); adapter.InsertCommand = cBuilder.GetInsertCommand(true); connection.Close();
Теперь при выполнении строки adapter.UpdateCommand = cBuilder.GetUpdateCommand(true); получаю ошибку о том, что требуется инициализировать свойство DataAdapter.SelectCommand. Хотя я, вроде как, прямо перед этим её инициализировал.


Ответ

Не дам полного ответа, так как очень давно не работал с winforms, но навскидку у вас в коде сразу две ошибки:
Вы создаете новый адаптер каждый раз при нажатии на кнопку, таким образом затирая все свои изменения, по уму этот код нужно перенести в Window_Load(). При создании адаптера создается только SelectCommand, а для связи с источником нужны еще UpdateCommand, InsertCommand и DeleteCommand. Их надо, либо создать ручками, либо если таблица не содержит связей с другими таблицами, можно воспользоваться OleDbCommandBuilder
UPD:
// должна быть для конкретного адаптера выполнена команда Fill(...) OleDbCommandBuilder cb = new OleDbCommandBuilder(adapter);
adapter.InsertCommand = cb.GetInsertCommand(); ... // прочие команды
Как то так, но я с адаптерами я не работал уже очень давно, поэтому вам лучше почитать, например здесь, правда там не OleDb*, но в принципе все аналогично.
UPD #2:
Добавлять команды в адаптер вы должны тоже в Window_Load сразу после
adapter.Fill(dTable);
Эта функция должна быть выполнена обязательно ДО, что бы адаптер смог получить схему для которой создает команды.
Насчет ошибки при вводе некорректных данных, абсолютно правильное поведение программы, поищите здесь на сайте были уже вопросы о валидации и инжекции кода , в общем это отдельная большая тема.
UPD #3:
Судя по сообщению, видимо нет. Там где вы делаете Update, поставте точку отладчика и посмотрите, чему равен InsertCommand, возможно вы в разных функциях используете разные адаптеры. Надо: сделать поле формы, в Window_Load - проинициализировать его, в Button_Click - вызвать функцию Update. А вы похоже в просто закинули ваш код в Window_Load - при выходе из которого адаптер просто уничтожается.
Ваш код должен выглядеть как то так:
public FormExemple : Form { protected string conStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db.mdb"; protected OleDbConnection connection; protected OleDbDataAdapter adapter; ....
Window_Load() { connection = new OleDbConnection(conStr); ... adapter.Fill(dTable);
cb = new OleDbCommandBuilder(adapter); adapter.InsertCommand = cb.GetInsertCommand(); ... }
Button_Click() { ... adapter.Update(dTable) ... } }

asyncio: корректная остановка не своих сопрограмм

Я опять с той же проблемой. В предыдущем вопросе все таски были под моим контролем, и я мог их вручную закрывать. Но вот беда: мне тут потребовались вебсокеты. В примерах в документации к одноимённому модулю никакого корректного закрытия не производится:
start_server = websockets.serve(hello, 'localhost', 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
Я поигрался с примерами, и да, при Ctrl+C ругается, если есть подключенный клиент:
$ python websocket_server.py ^C KeyboardInterrupt Exception ignored in: Task was destroyed but it is pending! task: wait_for= cb=[_wait.._on_completion() at asyncio/tasks.py:399]> Task was destroyed but it is pending! task: wait_for= cb=[_wait.._on_completion() at asyncio/tasks.py:399]> Task was destroyed but it is pending! task: wait_for=>
Так как корректного способа завершения в документации не представлено и быстрое тыкание наличия всяких server.close() ни к какому продуктивному результату не привело, снова появляется вопрос о корректном завершении приложения, если таски мной не контролируются.
Это просто эта библиотека кривая? Или это характерно для любых asyncio-библиотек и я чего-то не понимаю в самой сути asyncio и кто-нибудь ткнёт меня а какую-нибудь матчасть по этому поводу?
Как всё-таки корректно завершать всё это дело в общем случае?

С общим случаем принцип понятен, но websockets закрываться никак не хочет. Минимальный пример для повторения (на основе ответа @jfs):
#!/usr/bin/env python3 # -*- coding: utf-8 -*-
import asyncio import websockets
@asyncio.coroutine def echo(ws, path): print('client started') while True: data = yield from ws.recv() if data is None: break yield from ws.send(data) print('client finished')
loop = asyncio.get_event_loop() start_server = websockets.serve(echo, '127.0.0.1', 8888, loop=loop) server = loop.run_until_complete(start_server)
print('Listen') try: server = loop.run_forever() except KeyboardInterrupt: pass
server.close() loop.run_until_complete(server.wait_closed()) loop.close() print('Finished')
Достаточно просто подключить к нему любой клиент (хоть из браузера — ws = new WebSocket('ws://127.0.0.1:8888/');) и нажать Ctrl+C — отпечатается приведённая ранее ошибка (правда, уже без KeyboardInterrupt).


Ответ

asyncio реализует кооперативную многозадачность. Это значит, что websockets библиотека должна кооперировать (предоставлять возможность чисто завершить соединения). Был открыт баг на websockets Review cancellation management
Даже для обычных (preemptive) потоков не существует общего решения, которое бы корректно остановило бы потоки вне зависимости от кода, который они исполняют (Java выучила этот урок тяжёлым путём). Безопасным для остановки ресурсом является процесс операционной системы, если нас не волнует потеря данных из-за неочищенного файлового буфера, уведомления другой стороны о прекращении сетевого соединения, завершение транзакции базы данных итд. Если волнует, то процесс также обязан кооперировать, если мы хотим его завершить преждевременно.
Чтобы обработать Ctrl+C в Питоне, как обычно нужно поймать KeyboardInterrupt или определить свой обработчик для SIGINT сигнала. Пример echo сервера в asyncio документации показывает как это можно сделать (KeyboardInterrupt + wait_closed())
import asyncio
@asyncio.coroutine def handle_echo(reader, writer): data = yield from reader.read(100) message = data.decode() addr = writer.get_extra_info('peername') print("Received %r from %r" % (message, addr))
print("Send: %r" % message) writer.write(data) yield from writer.drain()
print("Close the client socket") writer.close()
loop = asyncio.get_event_loop() coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop) server = loop.run_until_complete(coro)
# Serve requests until CTRL+c is pressed print('Serving on {}'.format(server.sockets[0].getsockname())) try: loop.run_forever() except KeyboardInterrupt: pass
# Close the server server.close() loop.run_until_complete(server.wait_closed()) loop.close()
В gevent (кооперативная многозадачность) также необходимо KeyboardInterrtupt ловить.
Документация websockets.server.serve() явно упоминает:
serve() yields a Server which provides a close() method and a wait_closed() coroutine to stop serving requests.
то есть для websocket сервера можно точно такой же код использовать для корректной остановки:
try: loop.run_forever() except KeyboardInterrupt: pass
# Close the server server.close() loop.run_until_complete(server.wait_closed()) loop.close()

Как отсортировать контуры по расстоянию каждого с каждым?

На исходном изображении с помощью функции findContours() получен вектор контуров и вектор иерархической связи между ними:
std::vector > contours; std::vector hierarchy; cv::findContours(src_mat, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
Для быстрого доступа требуется сформировать сортированный список расстояний центральных точек каждого контура к центральным точкам других контуров из того же набора. Отдельный список нужен для того, чтобы сохранить связи в векторе, определяющем иерархию. Как это реализовать средствами OpenCV?


Ответ

Для решения этой задачи удобно использовать матрицу. В целом, предлагаемый к рассмотрению подход совершенно не привязан к сортировке именно по расстоянию между центральными точками контуров. Можно использовать такой показатель, как например разница площадей контуров (проще говоря, соотношение размеров) или любой иной параметр, представляющий из себя некую числовую разницу одного контура с другим.
Для начала требуется создать матрицу, содержащую значение желаемого параметра. Пусть это будет расстояние между центральными точками каждого контура к каждому.
const int vct_sz = contours.size(); const cv::Scalar def_val(0); cv::Mat val_mat(vct_sz, vct_sz, CV_64F, def_val);
Константа vct_sz - это просто количество контуров в исходном векторе. def_val - значение расстояния по умолчанию.
for(int row = 0; row < vct_sz; ++row) { const cv::RotatedRect rc1 = cv::minAreaRect(contours.at(row));
for(int col = 0; col < vct_sz; ++col) { if(row == col) continue;
if(val_mat.at(col,row) != 0) { val_mat.at(row,col) = val_mat.at(col,row);
} else { const cv::RotatedRect rc2 = cv::minAreaRect(contours.at(col));
val_mat.at(row,col) = (double)cv::norm(rc1.center - rc2.center); } } }
Теперь матрица значений val_mat полностью заполнена. Если её схематично нарисовать, то обнаружится примерно следующее:

В таблице индексы и строк, и колонок полностью соответствуют индексам в векторе контуров. Фактически, матрица получилась с зеркальным отражением данных. В какой-то мере это избыточно, но возможность моментального доступа к данным подчас окупает. В принципе можно и не заполнять зеркально, но матрица уже инициализирована double, поэтому экономии никакой не будет.
Остаётся лишь сортировка. Разумеется, что если отсортировать значения непосредственно в val_mat, то потеряется связь между индексами матрицы и индексами в векторе контуров. Во избежание этого строится матрица индексов:
cv::Mat idx_mat; cv::sortIdx(val_mat, idx_mat, cv::SORT_EVERY_ROW + cv::SORT_ASCENDING);
Теперь idx_mat будет содержать индексы матрицы val_mat, но отсортированные по значениям в строках последней:

На этом всё. Доступ по возрастающему расстоянию между проверяемым контуром и всеми остальными можно осуществлять через созданные матрицы:
// Индекс проверяемого контура. const int contour_idx = 0;
for(int i = 0; i < vct_sz; ++i) { const int &idx = idx_mat.at(contour_idx,i);
// Ближайший по расстоянию от проверяемого контур. const std::vector &contour = contours.at(idx);
// Расстояние от проверяемого до ближайшего контура. const double &distance = val_mat.at(contour_idx, idx); }

Подсчет шагов разных размеров - для более натуральной анимации Canvas

Здесь на русском StackOverflow я получил очень хороший совет и смог добавить анимацию фишек в буквенную игру для Android.
Когда пользователь выбирает в меню "Вернуть буквы" и буква "A" должна прилететь в позицию внизу экрана, показанную красной стрелкой:

Нынешний алгоритм работает так:
Текущие координаты фишки: mRect.top и mRect.left Конечные координаты фишки: mTarget.x и mTarget.y Пока на нем есть движущиеся фишки, экран мобильного приложения рисуется заново (для этого снова и снова вызывается postInvalidate(30)).
Когда нужно анимированно переместить фишку, я вызываю следующий метод, который делит предстоящий путь на 8 одинаковых шагов:
public void startAnimatedMove(float x, float y) { mTarget.x = x; mTarget.y = y; steps = 8; mStepX = (mTarget.x - mRect.left) / steps; mStepY = (mTarget.y - mRect.top) / steps; }
Каждый раз, по истечении 30 миллисекунд, вызывается метод, сдвигающий фишку в нужном направлении (причем последний шаг у меня особый - он помещает фишку точно на mTarget.x и mTarget.y - чтобы избежать ошибки округления):
private void nextStep() { if (--steps == 0) moveTo(mTarget.x, mTarget.y); else moveTo(mRect.left + mStepX, mRect.top + mStepY); }
Этот алгоритм работает хорошо (и дает возможность анимировать фишки прямо из главного thread-а приложения), но так как шаги mStepX и mStepY одинаковые, фишка движется неестественно.
Вопрос:
Как бы сделать шаги неодинаковыми? Они дожны быть большими в середине пути - и маленькими в начале и конце (см. зеленый график наверху справа). Тогда фишка будет медленно начинать и заканчивать движение - так называемый "easying in out".
То есть нужна функция (с использованием x*x или Math.sin()?) для каждой оси, которая получала бы как input номер текушего шага, а выдавала как output дистанцию в пикселях вдоль оси икс или игрек:
private void nextStep() { if (--steps == 0) moveTo(mTarget.x, mTarget.y); else moveTo(mRect.left + calcStepX(steps), mRect.top + calcStepY(steps)); }
Обновление:
Нашел интересный сайт про Easing Equations by Robert Penner, но пока не разобрался, как применить их к своему программному коду - особенно как рассчитать шаги, чтобы их сумма попала в точку назначения...


Ответ

как расчитать шаги, чтобы их сумма попала в точку назначения
Все формулы на упомянутом сайте опираются на 4 параметра: t, b, c, d
t – текущее время (или номер кадра), b – начальное значение, c – (конечное значение - начальное значение), d – общая длительность анимации.
Чтобы за 8 кадров некий параметр, напр., Y, изменился от 162 до 288, вы в понравившуюся формулу, напр., в экспонениальный out
return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b;
подставляете b,c вашей анимации, d=7, а t=просчитываемый кадр (0..7) и получаете на выходе значение вашего параметра для этого кадра.