Неожиданное продолжение этого поста (честно не ожидал резонанса), поэтому часть 2 хардкорных трюков, в которой речь пойдет немного о другом, пока подождет.
Итак, в двух словах, что изменилось: добавлен контрол и тестовое приложение для WindowsForms, вариант WPF немного изменился, рефакторинг-причесалинг, добавился threadsafe и контрол теперь может нормально ресайзиться в рантайме (включено в сэмплы, но не советую разворачивать на полный экран — это реально пугает). Спасибо камрадам, указавшим на ошибки и недостатки и теперь теперь проект гордо 0.5 beta. Можно сразу отправиться за обновлением на razorgdipainter.codeplex.com/, кому интересны подробности прошу под кат.
Речь пойдет о WinForms-варианте использования. Практически весь контрол:
Как и обещал, код прост, как сапог. Сразу обращает на себя внимание перенос GFX и BMP из кода тестового приложения в код контрола. Спасибо alexanderzaytsev, так и на душе спокойнее — фронтэнд-программеру будет сложнее сотворить что-то непотребное с ними, и с ресайзом контрола работать проще.
Еще обращает на себя внимание lock() в коде ресайза. Как я уже упоминал, одно из основных преимуществ библиотеки — потоконезависимость. Мы можем не обращать внимания на UI Thread, и рисовать на контроле из любого потока, хоть из трех разных одновременно. Мы ведь не хотим, чтобы кто-то пытался рисовать на битмапе, который в данный момент пересоздается из-за ресайза?
Соотвественно, фронтэнд-код оконного приложения тоже немного изменился:
Еще один lock(), но мы можем быть спокойны, что никто не сможет одновременно и рисовать и ресайзить.
В WPF-варианте практически те-же изменения. Данная реализация threadsafe конечно топорная, но для демо тестовых приложений годится.
В WF при закрытии окна успевает проскочить Exception — просто измените цикл рендера, потому-что:
Можно было бы в качестве дочернего контрола в WPF-варианте использовать контрол из WF-варианта, но что-то мне подсказывает, что лучше разделить WPF и WF ветки, поскольку при дальнейшем развитии могут возникнуть особенности использования, уникальные для каждой из архитектур. Но, возможно, я просто сам себя пугаю, и в дальнейшем ветки объединятся.
Пользуйтесь на здоровье под MIT-лицензией, т.е. вообще как угодно в любых целях. У кого есть желание — подключайтесь к развитию библиотеки на CodePlex.
UPD: Хабра-камрады sintez и nile1 убедили сделать
Действительно, и не сильно усложнилось, и правильнее стало. Обновил сорцы на кодеплексе, зарелизил 0.6 бета, спасибы всем участвовавшим написал.
Итак, в двух словах, что изменилось: добавлен контрол и тестовое приложение для WindowsForms, вариант WPF немного изменился, рефакторинг-причесалинг, добавился threadsafe и контрол теперь может нормально ресайзиться в рантайме (включено в сэмплы, но не советую разворачивать на полный экран — это реально пугает). Спасибо камрадам, указавшим на ошибки и недостатки и теперь теперь проект гордо 0.5 beta. Можно сразу отправиться за обновлением на razorgdipainter.codeplex.com/, кому интересны подробности прошу под кат.
Речь пойдет о WinForms-варианте использования. Практически весь контрол:
private readonly HandleRef hDCRef; private readonly Graphics hDCGraphics; private readonly RazorPainter RP; public Bitmap RazorBMP { get; private set; } public Graphics RazorGFX { get; private set; } public RazorPainterWFCtl() { InitializeComponent(); this.MinimumSize = new Size(1, 1); SetStyle(ControlStyles.DoubleBuffer, false); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.Opaque, true); hDCGraphics = CreateGraphics(); hDCRef = new HandleRef(hDCGraphics, hDCGraphics.GetHdc()); RP = new RazorPainter(); RazorBMP = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); RazorGFX = Graphics.FromImage(RazorBMP); this.Resize += (sender, args) => { lock (this) { if (RazorGFX != null) RazorGFX.Dispose(); if (RazorBMP != null) RazorBMP.Dispose(); RazorBMP = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); RazorGFX = Graphics.FromImage(RazorBMP); } }; } public void RazorPaint() { RP.Paint(hDCRef, RazorBMP); }
Как и обещал, код прост, как сапог. Сразу обращает на себя внимание перенос GFX и BMP из кода тестового приложения в код контрола. Спасибо alexanderzaytsev, так и на душе спокойнее — фронтэнд-программеру будет сложнее сотворить что-то непотребное с ними, и с ресайзом контрола работать проще.
Еще обращает на себя внимание lock() в коде ресайза. Как я уже упоминал, одно из основных преимуществ библиотеки — потоконезависимость. Мы можем не обращать внимания на UI Thread, и рисовать на контроле из любого потока, хоть из трех разных одновременно. Мы ведь не хотим, чтобы кто-то пытался рисовать на битмапе, который в данный момент пересоздается из-за ресайза?
Соотвественно, фронтэнд-код оконного приложения тоже немного изменился:
private void Render() { lock(razorPainterWFCtl1) { razorPainterWFCtl1.RazorGFX.Clear((drawred = !drawred) ? Color.Red : Color.Blue); razorPainterWFCtl1.RazorGFX.DrawString("habr.ru", this.Font, Brushes.Azure,10,10); razorPainterWFCtl1.RazorPaint(); } }
Еще один lock(), но мы можем быть спокойны, что никто не сможет одновременно и рисовать и ресайзить.
В WPF-варианте практически те-же изменения. Данная реализация threadsafe конечно топорная, но для демо тестовых приложений годится.
В WF при закрытии окна успевает проскочить Exception — просто измените цикл рендера, потому-что:
это действительно дурацкая идея для продакшна.renderthread = new Thread(() => { while (true) Render(); });
Можно было бы в качестве дочернего контрола в WPF-варианте использовать контрол из WF-варианта, но что-то мне подсказывает, что лучше разделить WPF и WF ветки, поскольку при дальнейшем развитии могут возникнуть особенности использования, уникальные для каждой из архитектур. Но, возможно, я просто сам себя пугаю, и в дальнейшем ветки объединятся.
Пользуйтесь на здоровье под MIT-лицензией, т.е. вообще как угодно в любых целях. У кого есть желание — подключайтесь к развитию библиотеки на CodePlex.
UPD: Хабра-камрады sintez и nile1 убедили сделать
с оговоркой на тестовой форме // better practice is Monitor.TryEnter() pattern, but here we do it simpler/// <summary> /// Lock it to avoid resize/repaint race /// </summary> readonly object RazorLock = new object();
Действительно, и не сильно усложнилось, и правильнее стало. Обновил сорцы на кодеплексе, зарелизил 0.6 бета, спасибы всем участвовавшим написал.