C#. Удаление HTML-тегов из текста.

Т.е. остается только текст между тегами, например из:
<b>жирный текст</b> <a href="http://example.org">Это ссылка куда-то</a>
должно получиться
жирный текст Это ссылка куда-то

Регулярное выражение для HTML-тега

По счастью, оно совсем простое:

<[^>]+>

Пример

Не забываем подключить соответствующее пространство имен:
using System.Text.RegularExpressions;
//...
string htmlText = "<html><head><title>tolik-punkoff.com</title></head> <body>Welcome to Tolik Punkoff blog!</body></html>";
OutputText = Regex.Replace(htmlText, "<[^>]+>", string.Empty);

//Содержимое OutputText:
//tolik-punkoff.com Welcome to Tolik Punkoff blog!

Примечание: В примере между фразами пробелы, потому что есть пробелы между тегами. Функция никаких пробелов сама по себе не вставляет!

Калькулятор пропорций

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

Особо каких-то отдельных вещей в программе нет (ввод чисел в TextBox объяснял ранее). Но тем не менее, может еще кому пригодится.

Скриншоты



+3

Исходники

Репозиторий на GitHub

Скачать

Готовую программу
Установщик

C# Ввод отрицательных чисел в TextBox

Преамбула

Продолжаем разговор о вводе чисел в TextBox. Сегодня будем вводить отрицательные числа, тобишь к числу (точнее строке) надо будет пририсовывать знак «минус».

Главное

Раз уж мы будем что-то в TextBox добавлять, то изменится длина строки в TextBox, а, соответственно текстовый курсор (|) перепрыгнет с позиции, на которой он находился, на следующую или предыдущую, что создаст неудобство пользователю. Это надо побороть.

Позицию курсора можно вытащить из свойства TextBox.SelectionStart, посему сохраним его перед началом всех действий с текстом в отдельную переменную:

int pos = txt.SelectionStart;

Теперь можно вводить знак числа.

Ввод знака числа

Смотрим, какой символ был введен. Если минус, то проверяем, был ли в начале строки минус. Был — убираем, не было — добавляем. В зависимости от того, убрали или добавили символ, корректируем местоположение текстового курсора, добавляя или удаляя позицию, если, соответственно, убрали или добавили символ.

//ввод минуса
if (e.KeyChar == '-')
{                
    if (txt.Text.StartsWith("-"))
    {
        txt.Text = txt.Text.Substring(1);
        txt.SelectionStart = pos - 1;
    }
    else
    {
        txt.Text = "-" + txt.Text;
        txt.SelectionStart = pos + 1;
    }

    e.Handled = true;
    return;
}

Дополнительно. Добавление лидирующего ноля.

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

//ввод точки (запятой)
if ((txt.Text.StartsWith(".")) || (txt.Text.StartsWith(",")))
{
    // добавление лидирующего ноля
    txt.Text = "0" + txt.Text;
    txt.SelectionStart = pos + 1;
}

if ((e.KeyChar == '.') || (e.KeyChar == ','))
{                
    if (txt.Text.Contains(".") || txt.Text.Contains(","))
    {
        e.Handled = true;
        return;
    }                

    return;
}

Вся функция целиком

На PasteBin

Дополнительно

1. Ввод в текстовое поле только цифр Копия
2. Ввод дробных чисел в текстовое поле Копия

Общий пример для всех заметок

— Ввод цифр
— Ввод чисел с дробной частью
— Ввод отрицательных чисел с дробной частью и добавление лидирующего ноля
— Дополнительно — пример конверсии вводимых строк в числа, обработка ошибок при конвертации.

На GitHub

C#. Ввод в TextBox чисел с дробной частью.

Т.е. нам необходимо ограничить ввод в текстовое поле (TextBox) только цифрами и одной точкой (и/или одной запятой). Далее пример кода (обработчика событий KeyPress), который позволяет вводить цифры и одну точку (или одну запятую).

private void txt_KeyPress(object sender, KeyPressEventArgs e)
{
    //ввод только цифр с одной точкой (запятой)
    if ((e.KeyChar == '.') || (e.KeyChar == ','))
    {
        TextBox txt = (TextBox)sender;
        if (txt.Text.Contains(".") || txt.Text.Contains(","))
        {
            e.Handled = true;
        }
        return;
    }

    if (!(Char.IsDigit(e.KeyChar)))
    {
        if ((e.KeyChar != (char)Keys.Back))
        {
            e.Handled = true;
        }
    }
}

На PasteBin

Дополнительно

Чтобы не заморачиваться, точка у вас или запятая, в строке, содержащей число с дробной частью, можно делать так:

using System.Globalization;
//...

public static double ToDouble(string Number)
{
    Number = Number.Replace(',', '.');
    NumberFormatInfo format = new NumberFormatInfo();
    format.NumberDecimalSeparator = ".";
    return Convert.ToDouble(Number, format);
}

На PasteBin

Класс Convert, к сожалению, зависит от языковых настроек системы, и если в качестве разделителя дробной и целой части в системе указана запятая, а в числе будет точка (или наоборот), то Convert.ToDouble(<число>) свалится с ошибкой.

Всех сопричастных с Днем программиста (и с Пятницей 13)!

Автоматическое получение конфигов и пароля к VPN от vpnbook, теперь и для Windows.

По многочисленным просьбам зрителей, сделал римейк собственных недавних скриптов (копия копия), только теперь для Windows.
Написал небольшую программулину на C#, которая делает то же самое, что и вышеописанные скрипты.
— для распаковки ZIP-архивов использовал библиотеку DotNetZip, она же Ionic.Zip (копия)
— а для распознавания пароля на картинке, не мудрствуя лукаво, вызвал tesseract, естественно, версию под Windows.
Она в архиве с готовыми бинарниками самой программы, единственное, что может потребоваться, это поставить VCRedist для Visual C++ 2015

Скриншоты


Получение пароля

Остальные под катом

Скачать

Программу
— Библиотеки для работы tesseract:
Visual C++ Redistributable for Visual Studio 2015 (c сайта Microsoft)
vc_redist.x86.exe
vc_redist.x64.exe

Исходники

На GitHub

C#. Простой парсинг HTML Regerp’ом.

Преамбула

Да, предваряя камни, которые в меня полетят. Так делать нельзя, неправильно и вообще некузяво. Но что я буду делать, если нормальных парсеров под .NET Framework 2.0 уже net, а задача маленькая — найти все теги <a> или <img> и выдрать из них, соответственно, значение атрибутов href или src.

Решение

public List<string> ParseTags(string Tag, string Property)
{
    List<string> listBuf = new List<string>();

    Regex reHref = new Regex(@"(?inx)
                                <" + Tag + @" \s [^>]*" +
                                    Property + @"\s* = \s*"+
                                        @"(?<q> ['""] )"+
                                            @"(?<url> [^""]+ )"+
                                         @"\k<q>"+
                                 @"[^>]* >");

    foreach (Match match in reHref.Matches(HTMLPage))
    {
        listBuf.Add(match.Groups["url"].ToString());
    }

    return listBuf;
}

Это немного видоизмененное решение (добавил возможность подставлять в регулярное выражение тег и атрибут) из одной статьи на Хабре. Заметка, как обычно, от склероза.

Плюс добавлена функция выбора из найденного, уже без всяких регулярных выражений, с помощью string.Contains(<строка>).

public List<string> Select(string Pattern, List<string> inputList)
{
    List<string> selectList = new List<string>();

    foreach (string s in inputList)
    {
        if (s.Contains(Pattern))
        {
            selectList.Add(s);
        }
    }

    return selectList;
}

C# Windows Forms. Использование ListView для логов, автоматическая прокрутка ListView, избавление от дрожания.

Преамбула

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

В него будем писать лог наших действий. Замутим тестовое приложение:

— Создадим форму с ListView и кнопкой «Начать»
— По нажатию кнопки «Начать» запустим отдельный поток, который будет выводить нам числа от 1 до 100, и генерировать событие.

Подготовка ListView

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

View = Details

Теперь идем в конструктор, ищем опцию Columns, и добавляем единственную колонку:
В появившемся окне все удаляем из поля Text, жмем OK, смотрим на размер (Size) ListWiev, возвращаемся в редактирование колонок, и правим свойство Width. Устанавливаем чуть меньше, чем размер самого ListWiev.

Отладили, посмотрели чтоб было красиво? Ставим

HeaderStyle = None (чтоб не отображался заголовок колонки, заголовки были убраны).

Первый тест

Несмотря на то, что все действия происходят в отдельном потоке, ListView дергается и дрожит:

https://youtu.be/6Un0TSmyw38

На GitHub

Избавляемся от дергания и дрожания

Для этого делаем новый контрол, наследник от ListView и в коде нового класса подправляем параметр отображения:

class MyListView:ListView
{
    public MyListView()
    {            
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }
}


Используем новый контрол вместо ListView. Ничего не дрожит и не дергается.

Автоматическая прокрутка ListView

При добавлении нового элемента:

Достаточно при добавлении в ListView устанавливать свойство TopItem в значение последнего элемента. Тогда у ListView появится автоматическая прокрутка:

lvOut.TopItem = lvOut.Items[lvOut.Items.Count - 1];

Результат


https://youtu.be/IzPIf5X8zQQ

Пример

На GitHub

C#. Работа с .ZIP архивами (подходит для старых .NET Framework’ов 3.5, 2.0)

Когда-то давно какой-то хороший человек написал библиотеку для работы с ZIP (а еще и Bzip2) архивами.

Приведу только простой пример использования — распаковка ZIP-архива в каталог:

public static bool UnzipToDir(string FileName,string UnzipDir)
{
    ZipFile zip = null;

    try
    {
        zip = ZipFile.Read(FileName);
        foreach (ZipEntry e in zip)
        {
            e.Extract(UnzipDir, 
                ExtractExistingFileAction.OverwriteSilently); 
                // перезаписывать существующие
        }
    }
    catch (Exception ex)
    {
        ErrorMessage = ex.Message;
        return false;
    }
    return true;
}

До использования, естественно, библиотеку надо подключить в References‘ах и прописать using:

using Ionic.Zip;

Источник (более подробное описание)

Работа с zip-архивами в .NET Framework 3.5 на C# Копия в PDF

Библиотека

1. Ссылка на Codeplex Archive
2. Скачать библиотеку с codernotes.ru
3. Копия архива Codeplex на nega.nz
4. Библиотека на Mega.nz

C#. Определить каталог пользователя.

Он же папка профиля пользователя, т.е., C:\Users\<имя пользователя>, например C:\Users\Tolik для пользователя Tolik.

Решение

Проще всего посмотреть в переменную окружения USERPROFILE:

Environment.GetEnvironmentVariable("USERPROFILE");

Для .NET Framework 4 и выше, путь к каталогу профиля пользователя добавлен в перечисление Environment.SpecialFolder под именем UserProfile. Таким образом, получить папку пользователя можно вот так:

Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

Пример

static void Main()
{
    string UserProfile = Environment.GetEnvironmentVariable("USERPROFILE");

    MessageBox.Show(UserProfile, "User profile folder path",
        MessageBoxButtons.OK, MessageBoxIcon.Information);
}

Пример на GitHub

IPInformer v 0.2.0

Наконец-то руки дошли прикрутить инсталлер и выложить 🙂

Программа предназначена для отображения внешнего IP-адреса компьютера, определения географической принадлежности IP-адреса, а также того, не попадает ли IP-адрес в список «запрещенных» стран.

Программа получает IP-адрес, из поставляемого с ней PHP-скрипта, размещенного на Web-сервере в Интернете, и/или (в текущей версии) с сайтов, которые информируют пользователя о его внешнем IP. В текущей версии не все такие сайты подходят (сайт не должен быть заскриптован, не требовать обязательного использования cookie и не выдавать капчу), а оригинальный скрипт PHP предоставляет некоторые дополнительные возможности.

Далее, программа получает из базы данных SxGeo сведения о геопозиции, и, если настроено и IP-адрес попал в стоп-лист стран, выводит предупреждение о попадании в данный список.

Свежую версию БД SxGeo можно бесплатно скачать с сайта разработчиков базы данных: https://sypexgeo.net/ru/download/

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

Изначально писалась по заказу одного активиста, у которого была проблема с периодическим отключением от VPN, в связи с чем, случайно мог «засветиться» его IP, или VPN мог переключить его на нежелательную страну.

По умолчанию программа запускается в «портативном» режиме (все данные хранятся в подкаталоге data каталога с исполняемым файлом)

Системные требования

Microsoft Windows XP и выше (Vista/7/8/8.1/10), .NET Framework 2.0 и выше, 512 Мб оперативной памяти, 15 Мб на жестком диске.

Дополнительные компоненты

В качестве контрола для ввода IP-адреса мы использовали C# IP Address Control вот этого автора:

https://www.codeproject.com/Articles/9352/A-C-IP-Address-Control

База данных SypexGeo (SxGeo):
© 2006-2018 zapimir
© 2006-2018 BINOVATOR

https://sypexgeo.net

Скриншоты

Получение нового IP

Остальные под катом

История версий

0.0.1 b, 19/01/2010 — Первая версия, написанная для товарища OPPosition
0.1.2 08/08/2018 — Исправлен код запросов и конфигов, многое переделано
0.2.0 01/07/2019 — Программа «отвязана» от скрипта с SxGeo на сервере, интерфейс к БД SxGeo перенесен внутрь программы, запросы к БД обрабатываются локально, добавлена поддержка других источников IP-адресов. Первая публичная версия.

Скачать

Портабельная версия
Инсталлятор

Исходники

Репозиторий на GitHub

C#: Определение версии, названия, сервис-пака и архитектуры Windows.

Преамбула

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

Подробности под катом

Исходники

Класс VersionDetect.cs
Весь пример целиком

Источники

Win32_OperatingSystem class
Тема на Киберфоруме
Вопрос на stackowerflow

Отложенная автозагрузка.

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

Возникла тут производственная задача. Некоторые программы, необходимые для работы, находятся на сетевых шарах (либо подключаемых/шифрованных контейнерах), однако, после загрузки рабочей ОС, они должны автоматически запускаться у пользователя. Для примера, на работе это была программа управления станком.

Решая задачу, перелопатил гору литературы, но ничего готового не нашел. В результате написал свой «Менеджер отложенной автозагрузки».

Принцип работы простой, пользователь в этом самом «Менеджере» указывает путь до нужной программы, лежащей, например, на сетевом диске. «Менеджер» установлен на разделе с ОС, и запускается при ее загрузке. При запуске он проверяет через заданный интервал времени, появился ли нужный «экзешник». Если появился — запускает.

По умолчанию программа работает в портативном режиме (т.е. все настройки и список запускаемых программ, хранятся в директории с программой). Запуск без параметров открывает окно настройки, где можно добавить нужные для запуска задачи, а также в отдельном меню установить время проверки доступности задач, прочие параметры и добавить сам менеджер в автозагрузку ОС. Понятно, что менеджер должен находиться на разделе жесткого диска, который доступен ОС сразу после загрузки.

В качестве запускаемой задачи можно указать не исполняемый файл (exe, com, bat, cmd), но и документ, тогда он откроется в ассоциированной с ним программе.

Для исполнения задач, необходимо запустить менеджер с параметром командной строки /run

Скриншоты


Основное окно настроек

Опции «Менеджера отложенной автозагрузки»

Окно добавления/редактирования задачи

Cправка по параметрам командной строки

StartupDelayed /help
StartupDelayed [/run] [/d|confdir <путь>]
/help
— эта справка
/run — выполнение задач
/d — запускать в «не-портативном режиме» (конфигурационные файлы в директории %LocalApplicationsData%\StartupDelayed)
/confdir <путь> — указать путь к директории с файлами конфигурации

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

Скачать

Инсталлятор (не портативная версия)
Портативная версия
Исходники на GitHub

DetecTOR v 0.3.0b.

Обновлена утилита DetecTOR, которая определяла, присутствует ли определенный IP в сети Tor.

По многочисленным просьбам зрителей добавлен:

— модуль SxGeoSharp, теперь, даже если IP отсутствует в сети Tor, программа возьмет информацию из базы SxGeo (если вы ее скачаете и подгрузите, она бесплатная), и выдаст вам страну (+город и регион, если есть) для конкретного IP-адреса.
— по умолчанию включен портабельный режим, все настройки программы хранятся в подкаталогах с исполняемым файлом.
— режим пакетной обработки. Если у вас есть огромный лог или просто список IPv4 адресов, (тестировалось на 1000 IP-адресов в специально нагенерированном текстовом файле, где IP были расположены в случайном порядке), то программа найдет все адреса, проверит их по БД SxGeo и БД адресов Tor и выдаст результат в виде файла CSV. Можно включить или отключить выдачу в отчет дополнительных данных Tor.
— добавлены дополнительные аргументы командной строки (см. readme.txt).
— проверено замечание пользователя [info]paperdaemon@ljr. Данная ошибка при работе в Windows 7 и более ранних версиях так и не была выявлена.

readme.txt

Основная статья о DetecTOR Копия
Скачать (портативная версия)
Исходники

C#, DataSet, пользовательские типы данных, и хинтик при использовании Dataset Designer.

Известно, что DataSet может хранить пользовательские типы данных в таблицах. Для нетипизированного DataSet, т.е. экземпляра класса DataSet, достаточно, чтобы нужные типы данных были видны из того места кода, в котором будем проводить операции с DataSet. Например, сделаем тестовый enum:

public enum testenum
{
     val0=0,
     val1=1
}

и подключим какое-нибудь дополнительное пространство имен, например:

using System.Diagnostics;

Теперь, в таблицу DataSet можно добавить поля типов testenum и, например, ProcessWindowStyle (из System.Diagnostics)

//...
DataSet dsTest = new DataSet();
//...
dsTest.Tables.Add("Test");
dsTest.Tables["Test"].Columns.Add("Text", typeof(string));
dsTest.Tables["Test"].Columns.Add("Enum", typeof(testenum));
dsTest.Tables["Test"].Columns.Add("Enum2", typeof(ProcessWindowStyle));

Код на PasteBin

Если же делать типизированный DataSet, т.е. добавить в проект DataSet, как отдельный класс (наследник обычного DataSet), и создать нужные таблицы в конструкторе (Dataset Designer), то при попытке просто прописать пользовательский тип DataType в конструкторе, получится ошибка:

На самом деле, имена типов данных нужно вводить полностью, вместе с их пространствами имен. Т.е., при условии, что пространство имен программы, например tmpDataSet, то тип testenum нужно указывать как tmpDataSet.testenum (а тип ProcessWindowStyle, соответственно, как System.Diagnostics.ProcessWindowStyle)


Вещь, вроде бы довольно очевидная, если приглядеться (стандартные типы из списка прописываются точно также):

Но почему-то прямо нигде не озвученная, что странно.

C#, Регулярное выражение для IP-адреса (v4)

Искать айпишники, например, в логах.
Для десятичной (полной) записи:

(25[0-5]|2[0-4]\d|[01]?\d\d?)(\.(25[0-5]|2[0-4]\d|[01]?\d\d?)){3}

Второе, должно поддерживать восьмеричную, шестнадцатеричную, десятичную и смешанную запись:

(0[0-7]{10,11}|0(x|X)[0-9a-fA-F]{8}|(\b4\d{8}[0-5]\b|\b[1-3]?\d{8}\d?\b)|((2[0-5][0-5]|1\d{2}|[1-9]\d?)|(0(x|X)[0-9a-fA-F]{2})|(0[0-7]{3}))(\.((2[0-5][0-5]|1\d{2}|\d\d?)|(0(x|X)[0-9a-fA-F]{2})|(0[0-7]{3}))){3})

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

Протестировать можно здесь
Ну и тесты на C# (от Лехи)

C#, Об анализе exceptions при вызове внешнего процесса.

Или отлавливаем нажатие клавиши «Отмена» в окне запроса UAC.

Вот однажды я писал небольшую утилиту, которая запускает любое приложение или командный файл (bat/cmd) от имени администратора. И мне в комментариях правильно намекнули, что я слишком грубо обрабатываю exceptions, которые могут случиться во время запуска внешнего процесса. Но вообще это хороший пример не только для конкретного случая, но и для подхода к обработке ошибок вообще. Кратко говоря — если вы предполагаете, что где-то может возникнуть ошибка, то есть два метода:
1. Предотвратить и обезвредить. К таким ошибкам, например, относится возможная недоступность файла для чтения/записи, или вообще его отсутствие, когда он нужен. Тогда лучше проверить, например, наличие файла, с помощью File.Exist() перед операцией с файлом.
2. Отловить на этапе времени выполнения. Для этого в C# существуют try/catch.

Нам нужен именно способ #2, поскольку мы не знаем и проверить заранее никак не можем, нажмет пользователь «Отмену» в окне запроса, или нет.

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

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

Немного теории об exceptions при запуске внешних процессов.

Как известно, в .NET ошибки времени выполнения распределены по классам. Есть общий класс — Exception, в который попадают все ошибки времени выполнения, и есть конкретные классы для обработки определенных ошибок. Иерархия обработки следующая: «от конкретных к общему». Т.е. сначала (если мы хотим их обработать), указываются конкретные ошибки, а потом можно, но не обязательно указать общий обработчик.
Конкретно при запуске внешних процессов могут возникнуть следующие виды ошибок:
ArgumentNullException
ObjectDisposedException
FileNotFoundException
(на самом деле в зависимости от OS но может не сработать, сработает следующий)
Win32Exception
он нам и нужен
PlatformNotSupportedException

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

Анализ Win32Exception

Для начала подключим нужный namespace:
using System.ComponentModel;

А теперь примемся за анализ. На самом деле, у каждой Win32-ошибки имеется внутренний код. В C#-exceptions он сохраняется в переменной NativeErrorCode. Т.е для решения нашей задачи, нам в конструкции try/catch достаточно отловить конкретный код ошибки. Для нажатия клавиши «Отмена» в окне UAC, это будет код 1223, «Операция отменена пользователем».

Исправление.

1. Сначала надо отловить ошибку типа Win32Exception и проанализировать значение NativeErrorCode.
2. Если NativeErrorCode == 1223, то не предпринимаем никаких действий.
3. Если NativeErrorCode другой, оповещаем пользователя об ошибке.
4. Если сработало исключение другого типа (не Win32Exception), то аналогично предыдущему пункту — оповещаем пользователя об ошибке.

//...

try
{
    Process.Start(psi);
}
catch (Win32Exception wex)
{
    if (wex.NativeErrorCode == 1223) //нажали "Отмену" в окне UAC
    {
        return true;
    }
    else //какой-то другой Win32 Error
    {
        ErrorMessage = wex.NativeErrorCode.ToString() + " " + wex.Message;
        return false;
    }
}
catch (Exception ex) //какой-то другой Exception
{
    ErrorMessage = ex.Message;
    return false;
}

//...

Код полностью здесь

Источники

1. Коды ошибок Win32 (Краткое пояснение и полный список кодов, англ., MSDN)
2. Нужный код ошибки (Отменено пользователем)

Утилита wsudo

1. Репозиторий на GitHub
2. Скачать
3. Заметка об утилите Копия

C#, программное создание ярлыка (shortcut) Windows

И случайно, самое полное описание WshShortcut.

Преамбула

Почему-то, уж не знаю почему, в .NET Framework (во всяком случае до 4 версии, в 4 вроде появился) не было стандартного способа создать ярлык (файл .LNK) программно. Но способы все-таки есть. Расскажу о них, в порядке уменьшения геморройности.

I. Создать ярлык вручную

Самый геморройный способ, для любителей ассемблера и прочего байтокопательства. Файл ярлыка (*.lnk), это обычный бинарный файл. Почему-то в сети бытует мнение, что формат LNK-файлов закрыт, и чуть ли не засекречен. Однако это не так, спецификация формата вполне себе открыта и лежит на официальном сайте Microsoft. Так что остается осилить 48 страничную спецификацию, и можно приступать. 🙂 Но мы этого делать не будем. Замечу лишь, что в формате файла есть несколько странных моментов. Например, зачем хранить в файле ярлыка серийный номер тома и тип диска (HDD, CD, Floppy) и NetBIOS имя компьютера, я совершенно не понимаю.

II. Обратиться к Windows Script Host через COM-интерфейс

Способ, наиболее часто встречающийся в сети, но почему-то, весьма поверхностно описанный. Windows Script Host — компонент Microsoft Windows, предназначенный для запуска сценариев на скриптовых языках JScript и VBScript, а также и на других дополнительно устанавливаемых языках (например, Perl).

Остановлюсь поподробнее на некоторых моментах. Сначала самое основное.
В References проекта надо добавить соответствующий компонент (щелкнуть по References правой кнопкой мыши, выбрать Add Reference…) В появившемся окне выбираем вкладку COM и находим компонент Windows Script Host Object Model.

Из-за того, что мы используем COM-интерфейс, с нашей программой придется таскать библиотеку для взаимодействия с ним Interop.IWshRuntimeLibrary.dll (ее нам без нашего участия сделает компилятор .NET).
Теперь указываем соответствующую директиву using:

using IWshRuntimeLibrary;

Создаем объект WSH Shell:

WshShell wshShell = new WshShell(); //создаем объект wsh shell
На самом деле у объекта WshShell довольно много интересных возможностей, например выполнять VBS или JS сценарии прямо из кода C#, со всеми возможностями Windows Scripting Host, естественно. Но это так, к слову. Мы же создадим объект для управления ярлыком:

IWshShortcut Shortcut = (IWshShortcut)wshShell.
CreateShortcut(ShortcutPath);

где ShortcutPath — строковая переменная, в которую записан путь к файлу создаваемого ярлыка.

В самом простейшем случае, надо задать имя файла для которого создается ярлык:

Shortcut.TargetPath = @"C:\Windows\notepad.exe"; //путь к целевому файлу

Если дополнительные параметры не заданы, то:
— в качестве иконки ярлыка будет установлена иконка по умолчанию (для EXE — его иконка, для остальных — стандартные системные иконки)
— в качестве рабочего каталога — каталог, в котором расположен целевой файл (тут C:\Windows\).
— размер окна — нормальный.

Теперь нужно сохранить ярлык:
Shortcut.Save();

Пример кода целиком на PasteBin

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

Пример кода функции, задающей дополнительные параметры ярлыка на PasteBin

Остальные параметры под катом

Горячая клавиша:
Задается параметром string Shortcut.Hotkey
Внимание! Если переменной попытаться установить значение null, произойдет ошибка нехватки памяти (OutOfMemoryException). Любая строка не подпадающая под формат, вызывает ArgumentException "Значение не попадает в ожидаемый диапазон."

Строка должна быть следующего вида: "Ctrl+Alt+N", т.е. содержать названия клавиш-модификаторов, символьную или функциональную клавишу, названия должны быть разделены знаком + без пробелов. Названия регистронезависимы.
Чтобы хоткей сработал, ярлык надо создавать или в меню Пуск, или на Рабочем столе. Почему-то если создать ярлык где-то еще, а потом скопировать в Пуск или на Рабочий стол, хоткей не работает (хотя, если менять горячую клавишу у уже созданного ярлыка, через свойства ярлыка, то все работает). Установленный хоткей становится глобальным для всей системы, т.е. если "Ctrl+Alt+N" обрабатывается в какой-то программе, то после создания ярлыка, сочетание клавиш будет перехвачено Windows, и запустится то, на что указывает ярлык.
Список возможных клавиш:
Модификаторы: CTRL+ ALT+ SHIFT+ (и еще какой-то EXT+ встречается в [1])
Алфавитно-цифровые, функциональные и прочие:
F1-F12, 0-9, A-Z
ESC, ENTER, TAB, SPACE, PRINT SCREEN
(указывается как SNAPSHOT), BACKSPACE [1] (причем обычным способом через проводник установить их нельзя, и нет, Ctrl+Alt+Del так не перехватить, хотя создать такой хоткей можно).
Полный список клавиш можно посмотреть в WINUSER.H или в [2], имена берутся без VK_, и не получится в качестве третьей клавиши использовать имена модификаторов и мышиных кнопок, ярлык создастся без ошибок, а вот работать не будет.

Забавный баг

Через свойства ярлыка нельзя задать горячую клавишу БЕЗ модификаторов. Windows заботливо будет нам подставлять CTRL+ALT+, а вот с помощью WshShortcut — можно, т.е. если значение Hotkey установить, например в «F1" и создать ярлык на Рабочем столе, то по нажатию F1 будет вызываться, например, Блокнот. На практике это использовать, конечно, никак нельзя, разве что над кем-нибудь подшутить.

Демо и класс-обертка над IWshShortcut на GitHub

III. Создание ярлыка через Windows API.

Вообще, этот способ по геморройности надо было бы ставить на второе место, чего одна статья [3], описывающая все API, стоит. Но авторы статьи, крутые акулы программирования, для нас постарались и таки сделали классы для работы с ярлыками, причем сделали великолепно! С помощью их класса ShellLink можно не только создавать новые ярлыки, но и читать/редактировать существующие.
ShellLink вместе с демо можно скачать с mega.nz или с моего репозитория на GitHub, не знаю, будут ли проблемы с лицензией, но на vbaccelerator.com вроде Creative Commons.
С официального сайта почему-то сей полезный горшочек пропал, хотя статья осталась. Видать авторы забили на проект и что-то протухло.
Повторюсь, класс написан хорошо, ничего не падает и с ошибками не вылетает. Единственное, что криво, это установка хоткея. Ну да и черт с ним, мне особо не нужно было, так что ковыряться и исправлять не стал.
Написано сие дело аж в 2003 г., но прекрасно работает до сих пор, на Windows 7 в т.ч.

Для подключения к своему проекту из оригинального архива понадобятся два класса (файлы ShellLink.cs и FileIcon.cs), далее подключаем соответствующий namespace (using vbAccelerator.Components.Shell;) и можно использовать. Пример кода:

ShellLink shortcut = new ShellLink();
shortcut.ShortCutFile = @"C:\Temp\shortcut\test.lnk";
shortcut.Target = @"C:\Windows\notepad.exe";
shortcut.WorkingDirectory = @"C:\";
shortcut.IconPath = @"C:\Windows\System32\shell32.dll";
shortcut.IconIndex=111;
shortcut.Description = "Тестовый ярлык";
shortcut.Arguments = "file.txt";
shortcut.DisplayMode = ShellLink.LinkDisplayMode.edmMaximized;
shortcut.Save();

Источники

1. WshShortcut.Hotkey Копия
2. Virtual-Key Codes Копия
3. Creating and Modifying Shortcuts (ShellLink, WebArchive) Копия
4. Создание ярлыков с помощью Windows Script Host
5. Спецификация формата файлов LNK Копия

Исходный код

1. ShellLink от vbaccelerator. Скачать с Mega.NZ. На GitHub
2. Демо и класс-обертка над IWshShortcut на GitHub

C# Получение пути к папке ОС Windows

Оказывается, вплоть до версии .NET 4 в перечислении Environment.SpecialFolder нет пути к папке, куда установлена Windows (обычно C:\Windows). Эту досадную оплошность можно обойти двумя способами:
1. Посмотреть в переменную окружения SystemRoot или windir:
string windir=Environment.GetEnvironmentVariable("SystemRoot");
string windir=Environment.GetEnvironmentVariable("windir");

2. ВНЕЗАПНО, бывают хитрые самосборные или специальные дистрибутивы, где данных переменных окружения нет. Тогда:
— надо взять путь к директории system, (обычно это C:\Windows\System32), который есть в Environment.SpecialFolder во всех версиях .NET: Environment.SpecialFolder.System
— получить директорию выше уровнем:

string windir = System.IO.Directory.GetParent(
        Environment.GetFolderPath(Environment.SpecialFolder.System));

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. — Содержание

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. Часть I — инициализация (и введение)
     -Преамбула
     -Общая структура БД
     -Заголовок базы данных
     -Дополнительные перечисления и мелкая корректировка с придирками.
     -Поля, свойства и конструктор класса
     -Функции для чтения БД
     -Функции для чтения заголовка
     -Закрытие базы данных
     -Открытие базы данных, чтение и проверка заголовка, чтение индексов.

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. — Часть II. Поиск.
     -Трехбайтовые числа
     -Байтовый substr
     -Поиск ID или смещения в «Диапазонах IP»
     -Функция поиска в «Диапазонах» (SearchDB)

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. — Часть III. Универсальный формат упаковки данных и получение данных из справочников.
     -Приведение типов
     -Анализ (распаковка) записи
     -Обработка полей записи
     -Дополнительные функции

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. — Часть IV. Получение данных.
     -Чтение информации из справочников
     -Поиск в справочнике по ID
     -Очистка ответа
     -Функция, формирующая финальный ответ.

SxGeoSharp. Интерфейс на C# для базы данных SypexGeo. — Приложение
     -Дополнительно
     -Источники
     -Код на GitHub
     -Создатели

Скачать все в PDF
SxGeoSharp на GitHub