Ежедневник IT-ка
133 subscribers
22 photos
4 videos
1 file
43 links
Программируем на практике!!
По вопросам по поводу проектов: @vladimir_dzen
Download Telegram
​​Продолжаем знакомство с программным комплексом ZennoPoster.

В примере показано:
- работа в многопоточном режиме
- чтение *.txt файлов
- сохранение содержимого *.txt файла в таблицу
- чтение и запись данных в таблицу
- конвертирование таблицы в список
- сохранение списка в *.txt файл

1. Получаем таблицу по имени созданную в проекте ZennoProject используя интерфейс IZennoTable

var sourceTable = project.Tables["Accounts"];

2. Инициализируем новый список класса List<T>

var sourceListFromTask = new List<string>();

3. Сохраняем в переменную "путь нашего файла", с которым работаем

string pathFile = Path.Combine(project.Directory, "account.txt");

4. FileLocker - это созданный нами объект синхронизации в общем коде ZennoProject, расположенный в классе CommonCode.

public static object FileLocker = new object();

4.1. Обязательно лочим наш поток(для того, чтобы другие
потоки не имели доступа к файлу),

lock (CommonCode.FileLocker)
{
4.2. Проверяем существование нашего файла, чистим таблицу
и инициализируем переменные.

if (File.Exists(pathFile))
{
sourceTable.Clear();
String line;
bool insertLine = true;

4.3. Читаем файл используя класс StreamReader,который
позволяет работать с файлом как хранилищем символов, и
такой подход более эффективен.

using (StreamReader sr = new StreamReader(@pathFile, Encoding.UTF8))
while ((line = sr.ReadLine()) != null)

4.4. Добавляем наши строки из файла в таблицу.

sourceTable.AddRow(line);

4.5. В цикле проверяем строки из таблицы и если выполняются необходимые условия, то сохраняем значения в переменные проекта.

for (int indexOfRow = 0; indexOfRow < sourceTable.RowCount; indexOfRow++)
{
string value = sourceTable.GetCell("C", indexOfRow).Trim();
if (string.IsNullOrEmpty(value) && insertLine)
{
project.Variables["emailAcc"].Value = sourceTable.GetCell("A", indexOfRow).Trim();
project.Variables["passAcc"].Value = sourceTable.GetCell("B", indexOfRow).Trim();
sourceTable.SetCell("C", indexOfRow, "Checking");
insertLine = false;
}
}

4.6. Переносим строки из таблицы в список, для того, чтобы
сохранить их в файл(использую  Linq запрос).

int column = 0;
Enumerable.Range(0, sourceTable.RowCount).ToList().ForEach(i => sourceListFromTask.Add(String.Join(":", sourceTable.GetRow(i))));

4.7. Сохраняем строки списка в файл используя класс
StreamWriter

using (StreamWriter sw = new StreamWriter(pathFile, false, Encoding.UTF8))
{
sw.WriteLine(string.Join("\r\n", sourceListFromTask));
}
}
}

Скриншот кода в проекте.

#ZennoPoster, #Сниппеты
​​Продолжаем знакомство с рефлексией, и будем получать информацию о классе и о методах в нём.

Рассматриваем проект, который реализовали ранее.

1. Открываем файл "MainWindow.xaml.cs", который был создан автоматически.

2.Подключаем пространство имён.

using System;
using System.Reflection;
using System.Windows;

3. Метод который описывает действие, которое происходит при нажатие на кнопку(которую создали в прошлый раз).

    private void GetHumanMethodNames_OnClick(object sender, RoutedEventArgs e)
    {

3.1. Имя нашего текстового блока, который расположен в
нашей разметке.

      Output.Text = "";

3.2. Поучаем экземпляр класса Туре, в котором прописываем
"полное имя типа(нашего класса) в строковом
представлении".

var infoAboutClass = Type.GetType("HumanizerDemo.Binance",false,true);

3.3. Устанавливаем размер шрифта, для нашего текстового
блока.

      Output.FontSize = 16;

3.4. Получаем разную информацию о классе с помощью
методов класса Type.

      Output.Text += "Получаем разную информацию о классе с помощью методов класса Type";
      Output.Text += Environment.NewLine;
      Output.Text += Environment.NewLine;
      Output.Text += "Полное Имя: " + infoAboutClass.FullName;
      Output.Text += Environment.NewLine;
      Output.Text += "Базовый класс: " + infoAboutClass.BaseType.ToString();
      Output.Text += Environment.NewLine;
      Output.Text += "Абстрактный: " + infoAboutClass.IsAbstract.ToString();
      Output.Text += Environment.NewLine;
      Output.Text += "Запрещено наследование: " + infoAboutClass.IsSealed.ToString();
      Output.Text += Environment.NewLine;
      Output.Text += "class: " + infoAboutClass.IsClass.ToString();
      Output.Text += Environment.NewLine;
      Output.Text += "Публичный: " + infoAboutClass.IsPublic.ToString();
      Output.Text += Environment.NewLine;
      Output.Text += Environment.NewLine;

3.5. Получаем информацию о методах, которые существуют в
этом классе.

      MethodInfo[] mi = infoAboutClass.GetMethods(BindingFlags.Instance
          | BindingFlags.Static
          | BindingFlags.Public
          | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);

      Output.Text += "Получаем информацию о методах";
      Output.Text += Environment.NewLine;
      Output.Text += Environment.NewLine;
      foreach (MethodInfo imi in mi)
      {
        Output.Text += "Полное Имя: " + imi.Name.ToString();
        Output.Text += Environment.NewLine;
      }
    }

Рассмотрел простейшие примеры, но уже и они показывают возможности рефлексии, которые помогают нам узнать некоторые вещи.

#Рефлексия, #Csharp
​​Работа со временем и датами - это одна из тех задач, которую необходимо делать в программирование очень часто. Ведь актуальность данных это, то на чём держится современный мир(ушёл от темы).
Мы также не будем отставать и будем работать со временем.

Задача: рассчитать среднее кол-во времени размещение постов в группе(наш случай) согласно сервиса telemetr.me .

Работаем в программном комплексе ZennoPoster, который отлично справляется с анализом структурой страницы.

1. Для того, чтобы получить все "времена длительности"(назовём это так), то мы должны понять в каком из тегов они находятся т.е. подготовить предварительные данные.
Из структуры страница сайта, мы понимаем, что вся нужная информация находится в одном главном для нас теге "tr". Находим его.

1.1. Обращаемся к свойству('ActiveTab') объекта('instance') и передаём полученную информацию в класс('Tab').

Tab tab = instance.ActiveTab;

instance - это объект, который позволяет получать информацию, которая находится в запущенном браузере(это очень грубое сравнение), но можно рассматривать и так.
Tab - это класс, который имеет свойства для работы с текущей вкладкой нашего браузера.

1.2. Получаем Все необходимые элементы, которые находятся в текущей вкладке нашего браузера, с помощью XPath запроса и передаём в класс('HtmlElementCollection' - который работает с группой элементов).

HtmlElementCollection entries = tab.FindElementsByXPath("//tr");

1.3. Если кол-во найденных элементов ('tr'), больше 0, то идём дальше.

1.4. Инициализируем переменные.

var lstTemp = new List<string>();
var totalTimePublicPostAdvertise = string.Empty;
var content = string.Empty;

if (entries.Count > 0)
{

1.5. Проходим в цикле по всем свойствам('InnerHtml'), полученных тегов tr, в которых и расположена нужная информация.

for (int x = 0; x < entries.Count; x++)
{
content = entries.Elements[x].InnerHtml;

1.6. ОБЯЗАТЕЛЬНО весь наш последующий
код должен быть расположен в
конструкции(try..catch), для того, чтобы при
возникновение ошибок, код понимал,
что делать в этом случае.

try{

1.7. Получаем информацию с необходимого тега('span' с названием класса 'kt-font-brand'), с помощью XPath запроса, используя метод ParseByXpath и сохраняем полученные данные в список.

lstTemp =
ZennoPoster.Parser.ParseByXpath(content,
"//span[contains(@class,'kt-font-brand')]",
"innerText").ToList();

totalTimePublicPostAdvertise =
lstTemp[0].Trim().Replace("'", @"''");

}catch (Exception ex){

1.8. Отправляем ошибку в лог ZennoPoster.

project.SendErrorToLog(ex.Message, true);
}
}

Продолжение следует...

#ZennoPoster, #Дата
Продолжаем работать со временем.
В прошлом примере, был получен список 'lstTemp' с временем.
Список выглядит так:

23 часов 58 минут,
23 часов 53 минут,
19 часов 9 минут,
23 часов 57 минут,
и т.д.

Наша цель преобразовать время, которое сейчас является типом данных "string" в тип данных "DateTime".
В C# существуют 2 метода(самые популярные), которые пытаются конвертировать "string" => "DateTime". Рассмотрим их более подробно.

1. DateTime.ParseExact(string s, string format, IFormatProvider? provider) - этот метод в качестве аргументов, кроме строки с датой, принимает еще строку в которой указан формат представления даты, а так же строку с указанием культуры, в которой принято такое представление.
Пример:

string time = "2021 Четверг октябрь 28 16:01";
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture("ru-RU");
DateTime dt = DateTime.ParseExact(time, "yyyy dddd MMMM dd HH:mm", culture);

Результат: 23.06.2020 14:35:00

2.DateTime.Parse(string s) - этот метод в качестве аргумента принимает строку с датой.
Пример:

string time = "23:58";
DateTime dt = = DateTime.Parse(time);

Результат: 28.10.2021 23:58:00

Методы существуют, но в нашем случае они не подходят т.к. не смогут распознать формат такого времени: "23 часов 58 минут".

Поэтому будет разбивать на определённые этапы:
1. Создаёт список с типом данных 'DateTime':

var listTimeTemp = new List<DateTime>();

2. Подготовим время в нормальный формат с помощью регулярного выражения, который поймёт метод: DateTime.Parse(string s):

tmpData = Regex.Replace(tmpData, @"ча.*?\s", ":");
tmpData = Regex.Replace(tmpData, @"мин.*", "");

после чистки, время уже будет иметь вот такой формат: "23:58"

3. Конвертируем и сохраняем полученное время в ранее созданный список 'listTimeTemp':

listTimeTemp.Add(DateTime.Parse(tmpData));

4. Сортируем полученное время в порядке возрастания с помощью 'Linq':

listTimeTemp.Sort((a, b) => a.CompareTo(b));

5. Создаёт список с типом данных 'double':

var totalMinutes = new List<double>();

6. Создаём цикл для списка 'listTimeTemp'( для того, чтобы получить минуты из часов и минуты из минут, соответственно. Формат времени из которого берутся эти данные:'28.10.2021 23:58:00'(пример)):

foreach (var hours in listTimeTemp)
{
int minutesFromHours = hours.Hour * 60;
int minutesFromMinutes = hours.Minute;
totalMinutes.Add(minutesFromHours + minutesFromMinutes);
}

7. Получаем среднюю сумму значения всех полученных минут из списка 'totalMinutes'.

var sum = totalMinutes.Sum() / listTimeTemp.Count;

8. Создаём объект 'TimeSpan', для того, чтобы из средней суммы, которую получили выше, получить часы и минуты:

TimeSpan ts = TimeSpan.FromMinutes(sum);
int midHours = ts.Hours;
int midMinutes = ts.Minutes;

9. Пример результата:

string res = ts.Hours.Tostring() + ":" + ts.Minutes.Tostring();

"22:52" - наше полученное искомое среднее время.

10. Пример всего кода:

var tmpData = string.Empty;
var listTimeTemp = new List<DateTime>();
for (x = 0; x < lstTemp.Count; x++)
{
tmpData = lstTemp[x].Trim();
tmpData = Regex.Replace(tmpData, @"ча.*?\s", ":");
tmpData = Regex.Replace(tmpData, @"мин.*", "");
listTimeTemp.Add(DateTime.Parse(tmpData));
}
listTimeTemp.Sort((a, b) => a.CompareTo(b));

var totalMinutes = new List<double>();
foreach (var hours in listTimeTemp)
{
int minutesFromHours = hours.Hour * 60;
int minutesFromMinutes = hours.Minute;
totalMinutes.Add(minutesFromHours +
minutesFromMinutes);
}
var sum = totalMinutes.Sum() / listTimeTemp.Count;
TimeSpan ts = TimeSpan.FromMinutes(sum);
int midHours = ts.Hours;
int midMinutes = ts.Minutes;
string res = ts.Hours.Tostring() + ":" + ts.Minutes.Tostring();

В этом примере рассмотрел те вещи, которые можно делать используя 'DateTime' и 'TimeSpan'.

#Csharp, #Дата
Сегодня буду говорить о База Данных и СУБД, в дальнейшем буду рассматривать конкретные примеры.
Для меня База Данных - это то место, где хранятся необходимые сведения, которые необходимо использовать в проектах(делаю то разделение, которое использую в своей специализации).

Это могут быть:

1. Текстовые файлы - просто огромное кол-во проектов и заказчиков, обожают хранить информацию в *.txt файлах.

2. Excel таблицы - различные форматы таблиц, самые популярные: csv, xls,xlsx и т.д.

3. Json - это подвид текстового формата с расширением *json, он очень удобен из-за своей структуры. При работе с сайтами, этот формат очень популярен. К примеру многие сайты, которые имеют API - выводят информацию в этом формате.

4. Xml - ещё один формат напоминающий json, но есть отличия и очень серьёзные, имеет расширение *.xml. Очень часто используются при загрузки на сайты, каких либо товаров, продуктов, также используются сайтами недвижимости(Yandex,Cian,Avito и др.)

5. MySQL - система управления базами данных. В современном мире без элементарного понимания работы с MySql, могут начаться большие трудности. Многие скрипты, проекты, веб-приложения используют эту систему. Один жирный минус - для неё нужен сервер.

6. SQLite - компактная встраиваемая система управления базами данных. Очень удобно её использовать т.к. не требует "лишних телодвижений". Создал файл(к примеру: *.db), в нём таблицу и подключился к ней, просто указав путь, "красота".

7. Microsoft SQL Server - парадоксально, но не смотря на то, что мною проекты созданы в основном на C#, эту СУБД использую крайне редко т.к. многим надо "выход в мир" и быть более свободными и не зависеть от привязки к Windows. Хотя эта СУБД, также очень популярна.

8. Firebase.google.com - это облачный сервис, в котором также можно создать свою Б.Д. В чём её преимущество: просто подключиться, доступность из любой точки мира, бесплатно(платный вариант также есть, но в основном он не нужен), в какой-то степени замена MySQL(но, очень условно т.к. есть отличия при работе с ней).

Перечислил то, что использую в своих проектах в 90% случаях. Понятно, что это только часть из того, что есть, но этого вполне мне хватает.

Много из всего этого, я буду рассматривать с своих примерах т.к. работа с ними имеет свой подход.
This media is not supported in your browser
VIEW IN TELEGRAM
Вот пример одного из скриптов, который помогает анализировать группы из ТГ и сокращает большое кол-во ручного труда.

Преимущества:

- Многопоточность
.

- Работа с СУБД - SQLite(
как писал выше, один файл, путь к Б.Д. и всё).

- Выборка групп(из сервиса и нашей Б.Д.), согласно критериям, которые были заданы в настройках программы.

- Сохранения данных в
docs.google.com

-
Подсчёт
средней величины размещения рекламных постов в днях и по времени на площадке.

-
Создание скриншотов, тех постов, которые принесли больше всего кол-во подписчиков, согласно выбранной тематике.

- Кол-во новых подписчиков(минимум, максимум, среднее) от тех рекламных постов, которые были размещены на площадке раньше.

- и др. фишки, которые
помогают делать анализ площадки.

Недостатки:

- Плата за использования
программного комплекса ZennoPoster.

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

- Ограниченное кол-во потоков.

Часть вещей, которые реализовал в этом скрипте, буду описывать в дальнейших постах.

#ZennoPoster
​​В прошлых постах публиковал о тех СУБД(MySql в частности), которые использую в своей практике и тут-же подвернулся интересный пример, который показывает 'МОЩЬ' языка программирования Sql.

Похожий запрос в практике встречается в 99% случаях.

Цель:
Сделать запрос, который укажет кол-во звонков с определенных площадок.

Дано:
Две разных таблицы в одной Б.Д.
1. ads_phones (первая таблица)
2. ads_calls (вторая таблица)

Решение:
SELECT ads_phones.note, ads_phones.phone, COUNT(ads_calls.call_to) AS count_call FROM ads_phones INNER JOIN ads_calls ON ads_calls.call_to = ads_phones.phone AND MONTH(ads_calls.created) = MONTH(NOW() - INTERVAL 1 MONTH) GROUP BY ads_calls.call_to HAVING COUNT(ads_calls.call_to) > 1 ORDER BY count_call DESC

Интересные моменты, на которые стоит обратить внимание.

а) Запрос формирует одну общую таблицу из двух(нескольких), где оператор ON - условие для сравнения(т.е. "номера телефонов" в табл. должны совпадать).
INNER JOIN ads_calls ON ads_calls.call_to = ads_phones.phone

б) Запрос делает выборку данных, за прошлый месяц.
MONTH(ads_calls.created) = MONTH(NOW() - INTERVAL 1 MONTH)

в) Запрос делает группировку данных согласно минимальному кол-ву, которые нам интересны(т.е. > 1).
GROUP BY ads_calls.call_to HAVING COUNT(ads_calls.call_to) > 1

Рекомендую сохранить пример этого запроса т.к. "Узнавать Кол-во" чего либо, приходиться очень часто.

#Sql, #MySql
Хочу сделать опрос. Интересны ли вам статьи связанные с такой виртуальной системой как Proxmox. Это выгодно только в том случае, если есть окупаемость(аренда сервера).

Да - 2
👍👍 13%
Нет - 11
👍👍👍👍👍👍👍👍 69%
Без разницы - 3
👍👍👍 19%
👥 16 человек уже проголосовало.
​​Необходимо передавать картинку Post/Get запросами. Подумав, а почему не передавать с помощью кодировки base64?

Пример: №1 (картинку в base64):

1. Путь к нашей картинке и переменную для нашей кодировки base64(тип данных string).

string pathFile = "D:\1_picture.jpeg";
string base64String = null;

2. Создаём кодировку base64 из нашей картинки, как строку(тип данных string).

2.1. Рекомендую использовать оператор "using" - для того, чтобы правильно освобождать,сбрасывать,удалять неуправляемые ресурсы(аналог метода Dispose)

2.2. Получаем нашу картинку из файла.

using (Image image = Image.FromFile(pathFile)
{
2.3. Для чтения/записи данных в память, в виде массива, используя класс MemoryStream. 

using (MemoryStream m = new MemoryStream())
{
2.4. Сохраняем нашу картинку в экземпляр класса MemoryStream.

image.Save(m, image.RawFormat);

2.5. Получаем массив для дальнейшей конвертации.

byte[] imageBytes = m.ToArray();

2.6. Получаем кодировку картинки в base64.

base64String = Convert.ToBase64String(imageBytes);
}
}

3. Полученную строку base64String, уже можем передавать, сохранять и делать с ней что угодно.

3.1. Сервис для просмотра полученной картинки из кодировки base64(тип данных string)

Продолжение следует....

#Csharp, #base64

Пример кодировки.
​​В предыдущем примере рассмотрел случай кодирования картинку в base64.

Теперь рассмотрим обратный случай.

Пример: №2 (base64 в картинку):

1. Инициализируем новые переменные: путь к нашей картинке и кодировку base64.

string pathFileNew = "D:\1_pictureCopy.jpeg";
string base64StringNew = base64String(полученная кодировка base64 из пред. примера);

2. Получаем массив из переменной base64StringNew.

byte[] imgBytes = Convert.FromBase64String(base64StringNew);

3. Используем оператор "using"(рекомендую) для работы с картинкой.

using (var imageFile = new FileStream(pathFileNew, FileMode.Create))
{

3.1. Создаём картинку из полученного массива и сохраняем её, согласно нашего пути (pathFileNew).

imageFile.Write(imgBytes, 0, imgBytes.Length);
imageFile.Flush();
}

Все эти случаи в практике используются довольно часто: сервисы для решения каптч, создание сайтов, работа с Б.Д.

Кодирование и декодирование в base64 - это очень полезная вещь.

#Csharp, #base64

Результаты кодирования и декодирования.
​​Как и обещал ранее, буду рассматривать некоторые примеры, которые были реализованы мною в этом скрипте (если будет интересна одноразовая услуга по сбору площадок, используя этот скрипт, пишите).

Очень часто при вёрстки макетов сайта, похожая информация находится в в одинаковых тегах(div,tr,td,span и т.д.), которые для удобства отображения находятся в отдельных классах. Для того, чтобы получить всю эту информацию, необходимо это учесть.

Поэтому в данном примере это учту и покажу xPath запрос, который помог справится с этим.

Работаю с программным комплексом ZennoPoster

1. Обращаемся к свойству('ActiveTab') объекта('instance') и передаём полученную информацию в класс('Tab') (более подробнее писал выше).

Tab tab = instance.ActiveTab;

2. С помощью XPath запроса передаём найденные элементы в класс 'HtmlElementCollection'

HtmlElementCollection entries = tab.FindElementsByXPath("//tr[starts-with(@class,'tr_even')] | //tr[starts-with(@class,'tr_odd')]");

Интересный момент, на который стоит обратить внимание. 

а) Тело самого запроса использует логический оператор "или (|)", это позволяет нам собирать информацию с одинаковых тегов, но у которых разное оформление( "class")

"//tr[starts-with(@class,'tr_even')] | //tr[starts-with(@class,'tr_odd')]"

3. Основную информацию об элементах получил, дальше необходимо её обрабатывать.

#ZennoPoster, #Xpath
​​Сегодня отойдём слегка от программирования и займёмся реестром Windows(и да, её также можно использовать как своеобразную Б.Д., где есть свои таблицы).

Очень часто возникает ситуация, когда есть у нас файл, который хотим использовать как "шаблон" для всех своих последующих проектов, чтобы он был под рукой.Поэтому, проще всего создавать его из "контекстного меню Windows".

Шаги, которые необходимо предпринять:

1. Переходим в директорию:
"C:\Windows\ShellNew" и сохраняем(копируем) необходимый файл, который будет нашим шаблоном.

2. Открываем "редактор реестра". Запустить его можно, нажатием клавиши "Windows + R", вбиваем "regedit" в окно "Выполнить" и нажать "Ок".

3. Переходим в раздел "HKEY_CLASSES_ROOT" и находим в нём другой раздел, название которого совпадает с расширением нашего файла(в данном случае .zp) и в ней должен быть след.-ий раздел "ShellNew" (если его нет, то необходимо создать).

4. В текущем разделе "ShellNew" - необходимо создать "Строковый параметр" со значением "STARTZENNOFILE.zp" (это мой пример шаблона, который создал ранее) и названием "FileName".

5. На этом всё, получаем удовольствия от создания новых проектов, используя готовый "Шаблон".

Также в дальнейшем планирую показать пару примеров, в которых показана работа с реестром, но как программист т.к. обычно там хранятся настройки программ.

#РеестрWindows
​​Продолжаю писать статьи связанные с созданием приложения используя WPF. Даю ссылку на свой прошлый пост, где касался слегка этой темы.

Планирую написать ряд статей по созданию приложения, которое отсылает текстовые сообщения в ТГ(телеграм), используя его API.

Почему? Потому что, написания приложений используя API ТГ в "тренде" и полагаю он ещё будет долгое время.

В этой части статьи покажу весь код XAML файла, который реализовал т.е. интерфейс самой программы.

Основной код:

<Window x:Class="TelegramBot.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TelegramBot"
    mc:Ignorable="d"
    Title="MainWindow" Height="150" Width="800">
  <DockPanel LastChildFill="True">
<Menu DockPanel.Dock="Top"> 
      <MenuItem Header="Данные">
        <MenuItem Header="Сохранить"
             Command=""/>
        <Separator/>
        <MenuItem Header="Выход"
             Click="MenuItem_Click"/>
      </MenuItem>
    </Menu>
    <ListView DockPanel.Dock="Left"
         Background="MidnightBlue"
         Width="140">
      <TextBlock Text="Новое сообщение:" Foreground="White"/>
    </ListView>
    <StatusBar DockPanel.Dock="Bottom">
      <StatusBar.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="100" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
          </Grid>
        </ItemsPanelTemplate>
      </StatusBar.ItemsPanel>
      <StatusBarItem>
        <TextBlock Text="Статус:" />
      </StatusBarItem>
      <Separator Grid.Column="1" />
      <StatusBarItem Grid.Column="2">
        <TextBlock Text="Не отправлено" />
      </StatusBarItem>
      <Separator Grid.Column="3" />
      <StatusBarItem Grid.Column="4">
        <TextBlock Text="Кол-во знаков:" />
      </StatusBarItem>
      <Separator Grid.Column="5" />
      <StatusBarItem Grid.Column="6">
        <TextBlock Text="259" />
      </StatusBarItem>
    </StatusBar>
    <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5*"/>
        <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
      <TextBox Grid.Column="0" AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
     HorizontalScrollBarVisibility="Auto"/>
    <Button Grid.Column="1" x:Name="buttonSentMessage" Width="60" Height="30" Content="Нажать" Click="Button_Click" />
    </Grid>
  </DockPanel>
</Window>

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

В результате создания приложения этот код будет корректироваться.

#WPF, #Csharp
​​Рассматриваю более детальнее код, который опубликовал в прошлой статье.

1. Это часть кода генерируется автоматически в которой объявляются пространства имён.

<Window x:Class="TelegramBot.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:TelegramBot"
  mc:Ignorable="d"
  Title="MainWindow" Height="150" Width="800">

Мне интересна, в данном случае, вот эта строка, где указываем размеры формы приложения:

" Title="MainWindow" Height="150" Width="800"

2. Контейнер DockPanel - его очень удобно использовать для создания стандартных интерфейсов.

Верхнюю и левую часть - можно использовать для меню.

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

Правую часть - для отображения дополнительной части.

Центр - здесь находится основное содержание.

<DockPanel LastChildFill="True">
..................
..................
..................
 </DockPanel>

Свойство LastChildFill="True" означает, что последний элемент(в нашем случае, это текстовое поле "TextBox" и кнопка "Button") заполняет всё оставшееся пространство. 

3. Элемент управления Menu (выделяю его специально, т.к. в него входят другие элементы) - который расположен в верхней части нашей DockPanel панели.

<Menu DockPanel.Dock="Top"> 
   <MenuItem Header="Данные">
    <MenuItem Header="Сохранить"
       Command=""/>
    <Separator/>
    <MenuItem Header="Выход"
       Click="MenuItem_Click"/>
   </MenuItem>
  </Menu>

3.1. <MenuItem Header="Данные"> - это название меню. Оно может состоять из нескольких т.е. :

<MenuItem Header="Данные">
<MenuItem Header="Сохранить">
<MenuItem Header="Выход"">

3.2. Свойство "Command" используется для паттерна MVVM(Model-View-ViewModel). О нём буду говорить, в других постах, если будет необходимо.

3.3. Элемент  <Separator/> - это понятно, "разделитель".

3.4. Событие Click="MenuItem_Click" с присвоенным ему именем. Оно будет обрабатываться в коде.

Продолжение следует....

#WPF, #Csharp
​​Продолжаю знакомить с WPF.
В предыдущем посте остановился на рассмотрении элемента управления "Menu".

В этом рассмотрю сразу несколько элементов управления(кратко) "ListView", "TextBlock", "StatusBar" и "Grid"

1. Часть кода, который буду рассматривать.

<ListView DockPanel.Dock="Left"
         Background="MidnightBlue"
         Width="140">
      <TextBlock Text="Новое сообщение:" Foreground="White"/>
    </ListView>
    <StatusBar DockPanel.Dock="Bottom">
      <StatusBar.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid>
            <Grid.ColumnDefinitions>
              ..................
              ..................
             ..................
            </Grid.ColumnDefinitions>
          </Grid>
        </ItemsPanelTemplate>
      </StatusBar.ItemsPanel>
..................
   ..................
   ..................
    </StatusBar>

2. Элемент управления ListView(главный элемент в нашем интерфейсе), расположен в левой части интерфейса DockPanel.Dock="Left". Не смотря на то, что он унаследован от класса другого элемента ListBox, является более "продвинутым" т.к. ещё можно добавлять "заголовки столбцов" т.е. у него есть свойство "View", которое имеет свой элемент(объект) GridView, который позволяет создавать более сложную структуры таблицы(со своими названиями столбцов)

2.1. Короткий пример(добавление "Заголовков " столбцов т.е. "Headers"):

<ListView.View>
   <GridView>
      <GridViewColumn>№</GridViewColumn>
<GridViewColumn>Кол-во штук</GridViewColumn>
<GridViewColumn>Цена</GridViewColumn>
   </GridView>
</ListView.View>

2.2. Элемент управления TextBlock - служит для вывода текстовой информации и имеет свойство Foreground="White". В котором устанавливается цвет, переднего фона элемента, в нашем случае "цвет текста".

3. Переходим к след. элементу управления StatusBar (главный элемент в нашем интерфейсе), располагается в нижней части интерфейса DockPanel.Dock="Bottom".

Интересный момент: Многие элементы управления имеют ограничения в оформлении и для того, чтобы убрать их необходимо использовать другие элементы, которые будут вложены в текущие.

3.1. Обратимся к свойству ItemsPanel у элемента StatusBar т.е. <StatusBar.ItemsPanel>

3.2. Далее обратимся к элементу ItemsPanelTemplate, который будет в какой-то степени отвечать за оформления нашего StatusBar.

Элемент ItemsPanelTemplate, необходимо будет использовать довольно часто при работе с WPF.

3.3. В ItemsPanelTemplate добавляем такой элемент управления, как Grid

Grid - ЭТО мощный и часто используемый контейнер, напоминающий обычную таблицу. Прошу запомнить это т.к. без него интерфейс напоминал "беспорядочность".

3.3.1. Элемент Grid имеет два интересных свойств:

а) <Grid.ColumnDefinitions> - для работы с размерами столбцов - шириной.
б) <Grid.RowDefinitions> - для работы с размерами столбцов - высотой.

На этом всё.

Продолжение следует.....

#WPF#Csharp
Отойду от темы WPF, и покажу довольно "тривиальную задачу".

Работают два разных приложения:

Одно создаёт текстовый файл и записывает в него содержимое.
Второе читает это файл.

Проблема заключается в том, что первое приложение не успевает записывать в него информацию, как тут же второе его считывает, получая при этом ошибку "Файл пуст".

Решение для этой проблемы весьма простое:

1. Создаём метод "IsLockedFile" с возвращаемым типом данных bool.

2. Создаём конструкцию try...catch, для того чтобы словить исключение.

3. Добавляем оператор "using" - для того, чтобы правильно освобождать,сбрасывать,удалять неуправляемые ресурсы(аналог метода Dispose)

4. Обращаемся к классу "File" и его методу Open с его параметрами(где задаём enum или ещё называют перечисление: FileMode.Open, FileAccess.Read, FileShare.None) и создаем конструктор класса "FileStream".

5. Если файл успешно открылся и прочитался, то возвращаем false(закрывая при этом объект класса FileStream), если нет то true. И в том и в том случае выходим из метода IsLockedFile.

6. В коде вызываю этот метод передавая путь к файлу. Используя цикл "while" т.е. этот будет работать до тех пор, пока возвращает true.

Пример кода:

string pathFile = "D:\Example.txt";
while (IsLockedFile(pathFile));

public bool IsLockedFile(string fileName)
{
try
{
using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
fs.Close();
return false;
}
}
catch (Exception ex)
{
return true;
}
return true;
}

Очень удобное вещь, особенно если работаете также в многопоточном режиме. Советую это сохранить и использовать.

#Csharp, #File
​​Продолжаю знакомство с WPF.
В прошлой статье остановился на рассмотрение такого элемента как Grid.

Одно из важных свойств этого элемента(и не только этого) ширина и высота.
Есть несколько вариантов установки размеров:
"Auto" - установка ширины элемента согласно его содержанию.
"100" - фиксированный размер.
"*" - пропорциональный размер(маленький лайфхак, хотите увеличить этот размер в 2 в 3 раза, просто сделайте запись таким образом "2*" или "3*" ).

Для того, чтобы необходимый элемент имел правильные очертания(в моём случае это элемент "Grid"), то рекомендуется комбинировать эти размеры по необходимости.

          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="100" />
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
          </Grid>

Завершаем знакомство с элементами ItemsPanelTemplate и StatusBar.ItemsPanel - которые отвечали за основное оформление(т.е. своего рода "заготовки") и перехожу к след.-му.

      <StatusBarItem>
        <TextBlock Text="Статус:" />
      </StatusBarItem>
      <Separator Grid.Column="1" />
      <StatusBarItem Grid.Column="2">
        <TextBlock Text="Не отправлено" />
      </StatusBarItem>
      <Separator Grid.Column="3" />
      <StatusBarItem Grid.Column="4">
        <TextBlock Text="Кол-во знаков:" />
      </StatusBarItem>
      <Separator Grid.Column="5" />
      <StatusBarItem Grid.Column="6">
        <TextBlock Text="259" />
      </StatusBarItem>

Здесь появляется новый элемент StatusBarItem. Если прошлые элементы StatusBar.ItemsPanel и ItemsPanelTemplate служили нам заготовкой(разметкой), то уже этот служит нам для заполнения нужной информацией.

Каждый элемент размещается в своей колонке, привязка к определенной колонке происходит таким образом: Grid.Column="1" , где "1" это номер нашей колонки. Таким образом добавив нужные элементы(TextBlock и Separator) и привязав их к нужным колонкам у нас получится готовый StatusBar.

Продолжение следует.....

#WPF,#Csharp
В прошлом посте про WPF, рассмотрел элемент: StatusBar.
В текущем коснусь такого элемента, как TextBox и некоторых его свойств.

    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5*"/>
        <ColumnDefinition Width="*"/>
      </Grid.ColumnDefinitions>
      <TextBox Grid.Column="0" AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
     HorizontalScrollBarVisibility="Auto"/>
      <Button Grid.Column="1" x:Name="buttonSentMessage" Width="80" Height="30" Content="Отправить" Click="Button_Click" />
    </Grid>

Свойства:
AcceptsReturn="True" - служит для того, чтобы переводить по нажатию на клавишу Enter курсор на следующую строку.
VerticalScrollBarVisibility="Auto" и HorizontalScrollBarVisibility="Auto" - служит для отображения полос прокрутки TextBox поддерживает свойства.

Элемент: Button - где ему дал имя в свойстве: x:Name="buttonSentMessage"(это имя будет использоваться в коде).
Надпись(содержание), которая отображается на самой кнопке находится в свойстве: Content="Отправить".
Событие: Click="Button_Click" с собственным названием.

В коде будет примерно так(при нажатие на эту кнопку, выскакивает сообщение "Кнопка была нажата!"):

private void Button_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Кнопка была нажата!");
}

На этом шаге с оформлением останавливаюсь.

Продолжение следует.....

#WPF,#Csharp
​​Продолжаю работать с ZennoPoster - ом. На этот раз задача состоит в том, чтобы отсылать сообщения в Telegram используя бота.

В начале, для того чтобы пользоваться им, необходимо получить токен доступа для него в telegram.

Наши действия:

1. Находим и добавляем в телеграм контакт @BotFather.
2. Заходим к нему в чат. Вызываем команду:  /start
далее:  /newbot.
3. Печатаем имя нашего бота(например имя моего бота "vkbotanswer").
4. Далее username(обязательно с окончанием "bot")
5. В ответном сообщение получаем токен нашего бота.
6. Дальше добавляем нашего бота(как "администратора") в необходимый канал и одновременно получаем id этого канала, перейдя по ссылке.

https://api.telegram.org/bot1351987:AAGvMM26veDkzXyV72L1PU_7L0jjkO/getUpdates

где: 1351987:AAGvMM26veDkzXyV72L1PU_7L0jjkO(полученный токен доступа).

Внимание: id канала имеет всегда знак "-" т.е. такой вид: -1005758311 .

7. На этом бот наш готов и мы можем отсылать наше сообщение(пример запроса):

https://api.telegram.org/bot1351987:AAGvMM26veDkzXyV72L1PU_7L0jjkO/sendMessage?chat_id=-1005758311&text=Приветствую!Меня зовут vkbotanswer и я готов к вашим командам.

Продолжение следует.....

#ZennoPoster