11.20.2008

Про RichTextBox в .NET 2.0 и перенос строк.

Многие знают про то, что RichTextBox поддерживает word-wrap или перенос строк. Но не многие знают - как именно управлять переносом строк. Не так давно я столкнулся со следующей проблемой: в данном контроле перенос строк автоматически начинается с white-space символа или знака препинания, т.е. если хотим вбить 100 символов подряд так чтобы они отображались в 3х строках с произвольным переносом между строк - то проблемы начинаются после того как в середину воткнуть дефис или пробел. Да... выглядит конечно же не очень :( Никакой информации по этому поводу я не нашел. Казалось бы, что это не беда и все можно поправить, но на этом кол-во проблем начало только расти: у контрола RichTextBox есть булево свойство WordWrap, но нет никаких других свойств по изменению поведения этого самого переноса строк. Перепробовал буквально все. Документации по вопросам word-wrapping, word-breaking, hyphernatition почти не нашел. Значит дотНЕТ просто не имеет такого на вооружении. Итак, откуда же можно потрогать сей компонент? Откуда он вообще взялся на .Net? Правильно, на MFC уже много парились с ним. Нельзя просто забыть тот момент, когда приходило осознание того, что нужно подгрузить специальную библиотеку для использования этого самого контрола! Тем более дотнетный компонент является всего лишь обмоткой давно известной вещи. Вследствие этого я обратиться к исходникам данного компонента - а именно к файлу RichTextBox.h и MSDN :) После недолгих поисков была найдена следующая весчь: 1. Оконное сообщение EM_SETHYPHENATEINFO и связанные с ним стили WBF_WORDWRAP и т.д 2. Оконное сообщение EM_SETWORDBREAKPROC и функция EDITWORDBREAKPROC Первое отпало сразу же т.к. MSDN сказал, что это используется только в asian-виндовсах. Второе же оказалось намного интереснее: т.к. в качестве параметров в callback-функцию EDITWORDBREAKPROC передавались стили типа WBF_BREAKAFTER и WBF_ISWHITE, ну и собственно указатель на текст. Заинтересовало :) Т.е. теперь можно создать свою функцию с собственными определениями где и как переносить текст в контроле. Окей, накидал собственно код:
[DllImport("user32.dll")]
extern static IntPtr SendMessage(IntPtr hwnd, uint message, IntPtr wParam, IntPtr lParam);

const uint EM_SETWORDBREAKPROC = 0x00D0;

delegate int EditWordBreakProc(IntPtr text, int pos_in_text, int bCharSet, int action);
event EditWordBreakProc myCallBackEvent;

int myCallBack(IntPtr text, int pos_in_text, int bCharSet, int action)
{
    return 0;
}

public Form1()
{
    InitializeComponent();

    richTextBox.Multiline = true;
    richTextBox.WordWrap = true;
    richTextBox.ScrollBars = RichTextBoxScrollBars.None;

    myCallBackEvent = new EditWordBreakProc(myCallBack);
    IntPtr ptr_func = Marshal.GetFunctionPointerForDelegate(myCallBackEvent);
 
 SendMessage(richTextBox.Handle, EM_SETWORDBREAKPROC, IntPtr.Zero, ptr_func);
}
Итак, теперь разберемся: я создал делегат, совпадающий с определением EDITWORDBREAKPROC, и подписал на него собственную функцию. Через маршалинг получил указатель на мою функцию - теперь можно сделать подмену. Потом взял handle самого контрола и послал ему сообщение EM_SETWORDBREAKPROC на изменение функции переноса строк. Что должна делать функция myCallBack ? Собственно она должна возвращать позицию, в которой следует разорвать строку, относительно текущей. Т.е. реально надо распарсить весь текст и по "какому-то" правилу сказать - "вот здесь будет перенос строки". Для моей задачи вполне хватило вернуть нулевую позицию. Т.е. текст тупо переносится на другую строчку без всяких правил разрыва. Да это же то что мне нужно! Ура!! В качестве дополнения: 1. для остальных случаев, где нужно обрабатывать строки и расставлять переносы, стоит все-таки получить строку из callback функции, это можно сделать с помощью следующего кода:
int myCallBack(IntPtr text, int pos_in_text, int bCharSet, int action)
{
string str = Marshal.PtrToStringUni(text);
...
}
2. Стили у контрола меняются после различных оконных сообщений - типа смены фокуса, enable/disable и т.п. Значит, для полной работоспособности вышеприведенного примера нужно кидать сообщение EM_SETWORDBREAKPROC после всех значимых событий. Хотелось бы вообще забыть про такие мелочи. Выход есть)) Создаем новый контрол, наследуемся от RichTextBox ну и... я думаю вы сами догадаетесь :) 3. Пример на MFC выглядел бы куда проще :)

10.18.2008

Начало

Если даже Линус Торвальдс завел блог... то и мне стоит по-пробовать =) Предположительно в этом блоге я буду размещать заметки по программированию и моих изысканий в этой области. Поживем - увидим как у меня получится вести этот лог.