Invalidate() — для чего он нужен?
Ребят, подскажите. Я уверен, что заблуждаюсь (иначе не было бы этого метода), но зачем он нужен, если есть Paint у PictureBox ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
public partial class Form1 : Form { Bitmap bmp; int p; public Form1() { InitializeComponent(); bmp = new Bitmap(canva.Width, canva.Height); p = 0; } public Bitmap draw(Bitmap bmp, int p) { for (int i = 0; i 20; i++) for (int j = 0; j 20; j++) { switch (p) { case 0: bmp.SetPixel(i + 1, j + 1, Color.Black); break; case 1: bmp.SetPixel(i + 1, j + 1, Color.Red); break; } } return bmp; } private void button2_Click(object sender, EventArgs e) { if (p == 0) p = 1; else p = 0; //canva.Invalidate(); в любом случае вызывается Paint } protected override void OnPaint(PaintEventArgs e) { //base.OnPaint(e); //canva.Image = draw(bmp, p); } private void canva_Paint(object sender, PaintEventArgs e) { /* if (p == 0) p = 1; else p = 0; */ canva.Image = draw(bmp, p); } }
И еще пару вопросов:
1) для чего используется base.OnPaint(e) ?
2) Как перерисовывать только область PictureBox? C помощью Invalidate,где в качестве параметра передавать область PictureBox ?
Invalidate c что это
Смысл типа void и его предназначение тесно связаны с основной, самой мощной (и при неумелом применении деструктивной) особенностью языка C — использование указателей. Поэтому сначала нужно разобраться, что такое указатели и для чего они нужны (кто не знает, что такое указатели, см. врезку ниже).
Для работы со встраиваемыми системами критически важно хорошо программировать на языке C и иметь четкое представление о смысле указателей (здесь приведен перевод статьи [1]). Указатель так важен потому, что он позволяет программисту получить доступ к памяти системы самым быстрым и эффективным способом (что для встраиваемых систем критически важно). Память системы организована как последовательность байт (1 байт состоит из 8 битов). Если, к примеру, общая память в системе имеет размер 128 байт, то здесь будет 128 доступных ячеек, каждая из которых будет байтом, который можно прочитать и записать. Все 128 ячеек памяти будут пронумерованы числами от 0 до 127 специальным способом, примерно так: 0000, 0001, 0002, . и т. д. Это число, связанное с каждым байтом, называется адресом ячейки памяти.
Ниже на рисунке для иллюстрации всей идеи показана организация памяти некогда очень популярной архитектуры 8051.

Указатель это переменная, которая содержит адрес ячейки памяти. Если, к примеру, адрес ячейки 2050H, то указатель используется для того, чтобы хранить в себе это значение адреса.
Примечание: адрес ячейки памяти это всегда положительное целое число. Диапазон адресов простирается от 0 (адрес первой ячейки памяти; часто этот адрес имеет специальное назначение, об этом позже) до положительной целочисленной константы (которая является адресом последней ячейки памяти).
[Переменные указателей]
Мы можем использовать переменные для хранения адресов памяти, и такие переменные известны как переменные указателей. Обычные переменные используются для хранения в себе значений каких-то данных определенного типа (char, int, float и т. д.). Перед использованием переменной в программе мы сначала её декларируем. Специальным образом нам нужно также декларировать переменные и для указателей – чтобы компилятор знал, что мы декларируем переменную как указатель (это не обычная переменная). Делается такая декларация с помощью оператора *, это так называемый оператор косвенного обращения на языке C (indirection operator), иногда его называют оператором разыменования.
Общий синтаксис декларации указателя следующий:
int *ptr;
Здесь мы декларировали переменную указателя с именем ptr, и этот указатель предназначен для указания на первую ячейку памяти, где находится значение типа int.
Почему для указателей нужно указывать тип данных? По некоторому адресу в памяти могут содержаться какие-то данные, и это понятно. И это может быть данные любого типа char, int, float, даже структура, и т. д. Разница между типами в том, что они могут занимать для себя разное количество памяти. Char требует 1 байт, int может требовать 2 байта (хотя это не всегда так), и float занимает 4 байта. Память, выделенная для всех этих типов это последовательность из нескольких непрерывно следующих друг за другом байт.
Давайте рассмотрим сценарий наподобие следующего, в программе определены 3 переменные:
char a;
int b;
float c;
Предположим, что память системы начинается с адреса 2000H. Теперь символьная переменная ‘a’ будет находиться по адресу 2000H (и займет в памяти 1 байт), тогда как int-переменная ‘b’ займет 2 байта и будет находиться по адресам 2001H и 2002H. И наконец, последняя float-переменная ‘c’ займет 4 байта, и они будут находится в расположенных друг за другом байтах памяти с адресами 2003H, 2004H, 2005H, 2006H. Теперь Вы можете догадаться, зачем надо указывать типы данных, чтобы объявить переменную указателя. Причина в том, что области памяти под переменные выделяются в последовательных, находящихся друг за другом байтах памяти, и количество выделенных байт зависит от типа переменной, которая в них содержится.
Примечание: показанное в этом примере распределение памяти типично для 8-разрядных систем, таких как MSC-51 и AVR. Для 16-битных и 32-разрядных систем реальное распределение памяти для переменных может быть другим, что связано с выравниванием данных в памяти с целью более эффективного использования особенностей процессора.
Таким образом, когда мы декларируем переменную указателя как float *ptr, и затем присваиваем ей адрес обычной float-переменной c, то для компилятора устанавливается привязка указателя ptr ко всей области памяти от 2003H до 2006H. Но сама переменная ptr будет хранить в себе только начальный адрес блока памяти переменной (т. е. в нашем примере это 2003H), а тип указателя будет указывать для компилятора размер этого блока.
Следовательно, чтобы компилятор мог корректно интерпретировать содержимое памяти, соответствующее указателю, для указателя должен быть при декларации указан тип данных. И этот тип данных должен совпадать с типом данных, которые находятся по адресу переменной – тому адресу, который присваивается переменной указателя. Например, если адрес 2000H будет присвоен указателю ptr, то указателю будет соответствовать память, в которой находится символьная переменная ‘a’. В этом случае переменная указателя ptr должна быть декларирована с типом char, как показано ниже:
char *ptr;
Примечание: фактически мы могли бы декларировать переменную указателя без какого либо типа данных, используя ключевое слово void. Тогда получится так называемый пустой указатель.
[Присваивание адреса переменной указателя]
Чтобы можно было использовать указатель и его возможности, указателю должен быть присвоен адрес переменной. Указателю можно присвоить адрес как одиночной переменной, так и адрес массива, так и адрес структуры, и адрес переменной класса, и даже адрес переменной указателя. Это делает указатели особенно мощным (но и достаточно опасным при неумелом использовании) инструментом в программировании на языке C. Мы можем играючи обращаться с памятью системы, используя указатели.
Чтобы присвоить адрес переменной указателя мы используем оператор & (оператор взятия адреса переменной). Оператор & возвратит начало места в памяти, где расположена переменная. Пример:
void main() < int *ptr; // Декларация переменной указателя типа int. int a; // Декларация обычной переменной типа int. ptr = &a; // Присваивание адреса переменной a указателю ptr. >
[Обращение к содержимому памяти по указателю]
Теперь мы знаем, как присваивать адрес переменной указателя. Но как можно в программе обратиться к содержимому переменной, адрес которой присвоен указателю? Для этого мы используем тот же оператор косвенного обращения *, который мы использовали для декларации переменной указателя. Операция взятия значения по указателю также называется разыменованием указателя.
void main() < int *ptr; // Декларация переменной указателя ptr на тип int. int a = 10; // Декларация обычной переменной a, и присваивание ей значения 10. ptr = &a; // Присваивание адреса переменной a указателю ptr. // Следующий оператор позволит увидеть значение переменной a, полученное // с помощью указателя ptr: printf("Значение, на которое показывает указатель = %d", *ptr); >
Запуск этого кода выведет следующую строку:
Значение, на которое показывает указатель = 10
[Арифметические операции над указателями]
С типизованным указателями указателями (т. е. с такими указателями, для которых для декларации явно указан тип) можно использовать многие операции, которые доступны с целыми числами без знака: декремент —, инкремент ++, прибавление и вычитание константы. При этом будет меняться адрес, сохраненный в указателе на величину, кратную размеру типа указателя. Например, если прибавить к указателю на float константу 1, то сохраненный в указателе адрес увеличится на 4, потому что размер типа float равен 4 байтам.
void main() < float *ptr; ptr = (float*)(0x2000); //Выведется значение 0x2000: printf("ptr = 0x%04X\r\n", ptr); ptr = ptr+1; //Выведется значение 0x2004: printf("ptr = 0x%04X\r\n", ptr); >
[Указатели void на языке C]
Обычно переменная указателя декларируется с указанием типа данных содержимого, которое хранится в том месте памяти, на которое ссылается указатель (перевод статьи [2]). Примеры:
char *ptr1;
int *ptr2;
float *ptr3;
Переменная указателя, декларированная на определенный тип, не может содержать в себе адрес переменной другого типа. Это неправильно, и приведет к сообщению об ошибке при компиляции. Пример:
char *ptr;
int var1;
ptr = &var1; // Это неправильно, потому что ptr должен указывать на переменную типа char.
На языке C есть возможность создать указатель на неопределенный тип, так называемый «пустой указатель» (void pointer). Указатель на void это просто переменная указателя, которая декларирована с зарезервированным на языке C ключевым словом void. Пример:
void *ptr; // Теперь ptr это переменная указателя общего назначения
Когда указатель декларируется с ключевым словом void, он становится универсальным. Это значит, что ему может быть присвоен адрес переменной любого типа (char, int, float и т. д.), и это не будет ошибкой.
[Разыменование void-указателя]
Как делается разыменование типизованных указателей с помощью оператора *, Вы уже знаете (если нет, то см. врезку «Что такое указатель»). Но в случае указателя на void нужно использовать приведение типа (typecast) переменной указателя, чтобы выполнить её разыменование (выполнить обращение к содержимому памяти, на которую ссылается void-указатель). Причина в том, что с void-указателем не связан никакой тип, и для компилятора нет никакого способа автоматически узнать, как обращаться к содержимому памяти, связанному с void-указателем. Таким образом, чтобы получить данные, на который ссылается void-указатель, мы делаем приведение указателя к корректному типу данных, которые находятся по адресу, содержащемуся в void-указателе.
void main() < int a = 10; float b = 35.75; void *ptr; // Декларация void-указателя.
ptr = &a; // Присвоение адреса целочисленной переменной void-указателю.
//Ниже приведен пример взятия значения целочисленной переменной //с помощью void-указателя. Здесь (int*)ptr используется для //приведения типа, и *((int*)ptr) будет ссылаться на содержимое //памяти, где находится переменная a. printf("Значение целочисленной переменой = %d", *((int*)ptr));
ptr=&b; // Присвоение адреса переменной типа float void-указателю.
//Ниже приведен пример взятия значения float-переменной с помощью void-указателя. printf("Значение переменной типа float = %f", *((float*)ptr)); >
Указатели void полезны для программиста, когда заранее неизвестно о типе данных, которые поступают на вход программы. Типичным примером могут служить библиотечные функции манипулирования блоками памяти memcpy, memset и т. п. С помощью void-указателя программист может сослаться на место размещения данных произвольного, заранее неизвестного типа данных. Программа, к примеру, может быть написана таким образом, чтобы запросить у пользователя, какое приведение типа нужно использовать, чтобы правильно обработать входные данные. Ниже приведен в качестве примера кусок подобного кода.
void funct (void *a, int z) < //Значение переменной z будет показывать, на какой тип данных //ссылается параметр a, что будет использоваться для корректного //приведения типа. if(z==1) < //Пользователь ввел 1, что означает целый тип данных. printf("%d", *(int*)a); //приведение к типу int > else if(z==2) < //2 соответствует символьному типу данных. printf("%c", *(char*)a); //приведение к типу char > else if(z==3) < //3 соответствует данным с плавающей точкой. printf("%f", *(float*)a); //приведение к типу float > >
При использовании void-указателей следует помнить, что для них недопустимы арифметические операции, как для типизованных указателей (см. врезку «Что такое указатель»). Пример:
void *ptr;
int a;
//Следующий оператор инкремента недопустим и приведет к ошибке при компиляции,
//потому что компилятор не получил никакой информации о том, на какую величину
//нужно изменить адрес, хранящийся в void-указателе. ptr++;
[Ссылки]
1. Introduction to pointers in C site:circuitstoday.com .
2. Void pointers in C site:circuitstoday.com .
Урок №29. Тип данных void
Тип void — это самый простой тип данных, который означает «отсутствие любого типа данных». Следовательно, переменные не могут быть типа void:
void value ; // не будет работать, так как переменная не может иметь тип void
Тип void, как правило, используется в трех случаях:
Использование №1: Указать, что функция не возвращает значение:
void writeValue ( int x ) // здесь void означает, что функция не возвращает никакое значение
std :: cout << "The value of x is: " << x << std :: endl ;
// Нет стейтмента return, так как тип функции - void
Использование №2: Указать, что функция не имеет никаких параметров (перешло из языка Cи):
int getValue ( void ) // здесь void означает, что функция не имеет никаких параметров
std :: cin >> x ;
Указание типа void как «никаких параметров» является пережитком, сохранившимся еще со времен языка Cи. Следующий код равнозначен и более предпочтителен для использования в языке C++:
int getValue ( ) // пустые скобки означают то же, что и void
std :: cin >> x ;
Правило: Используйте пустой список параметров вместо void для указания отсутствия параметров в функции.
Использование №3: Ключевое слово void имеет третий (более продвинутый) способ использования в языке C++, который мы будем рассматривать на уроке №92.
(743 оценок, среднее: 4,92 из 5)
Урок №28. Инициализация, присваивание и объявление переменных
Урок №30. Размер типов данных
Комментариев: 5
Ключевое слово void имеет третий более продвинутый.
Этот void какой то эпический, уже 2й раз читаю что о нем мы узнаем дальше))) Мне кажется это пасхалка какая-то))
Константин П :
Если все-таки разделять понятия «процедура» и «функция» (понимаю, что в C понятие «процедура» отсутствует), то void станет более понятным.
Процедура — это просто последовательность действий.
Функция — это как бы «ответ на вопрос».
«Отсортируй переданный в параметрах массив» — процедура (void).
«Что получится, если перемножить переданные параметры?» — функция.
Invalidate c что это

C# для профессионалов
Для кого предназначена эта книга
Основные темы книги
Платформа .NET предлагает новую среду, в которой можно разрабатывать практически любое приложение, действующее под управлением Windows, а язык C# — новый язык программирования, созданный специально для работы с .NET.
В этой книге представлены все основные концепции языка C# и платформы .NET. Полностью описывается синтаксис C#, приводятся примеры построения различных типов приложений с использованием C# — создание приложений и служб Windows, приложений и служб WWW при помощи ASP.NET, а также элементов управления Windows и WWW Рассматриваются общие библиотеки классов .NET, в частности, доступ к данным с помощью ADO.NET и доступ к службе Active Directory с применением классов DirectoryServices.
Эта книга предназначена для опытных разработчиков, возможно, имеющих опыт программирования на VB, C++ или Java, но не использовавших ранее в своей работе язык C# и платформу .NET. Программистам, применяющим современные технологии, книга даст полное представление о том, как писать программы на C# для платформы .NET.
• Все особенности языка C#
• C# и объектно-ориентированное программирование
• Приложения и службы Windows
• Создание web-страниц и web-служб с помощью ASP NET
• Доступ к данным при помощи ADO NET
• Создание распределённых приложений с помощью NET Remoting
• Интеграция с COM, COM+ и службой Active Directory
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книги автора: C# для профессионалов. Том IIProfessional C#
Книга: C# для профессионалов. Том II
Метод Invalidate()
Скрыть рекламу в статье
Invalidate() является членом System.Windows.Forms.Form , с которым мы еще не встречались. Это очень полезный метод для случая, когда что-то необходимо перерисовать. По сути он отмечает область клиентского окна как недействительную и поэтому требующую перерисовки, а затем обеспечивает инициирование события Event . Существует две перезагружаемые версии метода Invalidate() : можно передать ему прямоугольник, который точно определяет (в координатах страницы), какая область окна требует перерисовки, или, если не передается никаких параметров, вся клиентская область помечается как недействительная.
Почему мы делаем это таким образом? Если мы знаем, что требуется что-то нарисовать, почему не вызвать просто OnPaint() или другой метод? При необходимости сделать на экране очень точное небольшое изменение, можно так и поступить, но обычно вызов процедур рисования напрямую рассматривается как плохая практика программирования. Если код решает, что требуется выполнить некоторое рисование, обычно вызывается метод Invalidate() .
Для этого существует несколько причин:
? Рисование почти всегда является наиболее интенсивно использующей процессор задачей, которую будет решать приложение GDI+. Выполнение его в середине другой работы задерживают эту работу. Для нашего примера, если бы метод рисования вызывался прямо из метода LoadFile() , то метод LoadFile() не вернул бы управление, пока не завершена задача рисования. В течение этого времени приложение не сможет ответить ни на одно иное событие. С другой стороны, вызывая метод Invalidate() , мы просто поручаем Windows инициировать событие Paint перед непосредственным выходом из LoadFile() . Система Windows тогда сможет проверить события, ожидающие обработки. Внутренне это происходит следующим образом. События находятся в так называемых сообщениях, которые выстраиваются в очередь. Система Windows периодически проверяет очередь сообщений, и если в ней есть события, Windows берет одно из них и вызывает соответствующий обработчик событий. С большой вероятностью событие Paint — единственное событие, находящееся в очереди, поэтому OnPaint() будет немедленно вызван в любом случае. Однако в более сложных приложениях могут быть другие события, некоторые из них имеют приоритет. В частности, если пользователь решил покинуть приложение, это будет отмечено сообщением в очереди, известном как WM_QUIT . Обработка такого события будет иметь самый высокий приоритет. Это очевидно, так как выполнение, например обновления графики в окне приложения, которое в данный момент будет закрыто, не имеет смысла. Таким образом, использование для рисования области метода Invalidate() сортировки запросов означает, что приложение действует, как хорошо ведущее себя приложение Windows.
? В случае более сложного, мультипоточного приложения желательно, чтобы только один поток выполнения обрабатывал все рисование. Использование метода Invalidate() для направления всего рисования в очередь сообщений предоставляет гарантию, что один и тот же поток выполнения (какой бы поток выполнения ни отвечал за очередь сообщений, это будет поток выполнения, который вызвал Application.Run() ) произведет рисование безотносительно к тому, какие другие потоки выполнения запрашивают операцию рисования.
? Существует также причина, связанная с производительностью. Предположим, что примерно в одно время поступает пара различных запросов для рисования части экрана. Возможно, что код только что сделал изменение документа и хочет убедиться, что обновленный документ выводится, а в то же самое время пользователь восстановил минимизированное окно или переместил другое окно, которое закрывало часть клиентской области. Вызывая Invalidate() , мы даем знать окну, что это произошло. Windows может затем объединить события Paint , если это возможно, комбинируя недействительные области, так что рисование будет выполняться только один раз. (Вызов метода для выполнения рисования прямо из кода может привести к ненужному результату, когда одна и та же область экрана будет перерисовываться более одного раза.)
? Наконец, код для выполнения рисования будет, вероятно, одной из наиболее сложных частей кода приложения, особенно если у приложения достаточно развитый интерфейс пользователя. Те, кому придется модифицировать этот код через пару лет, будут благодарить разработчика за сохранение кода рисования в одном месте и за максимально возможную простоту — чего добиться гораздо легче, если код не разбросан по разными частям программы.
Из этого можно сделать вывод, что хорошая практика состоит в поддержании всего кода рисования в процедуре OnPaint() или в других методах, вызываемых из этого метода. Не создавайте в коде множества других мест, которые вызывают методы для реализации странных фрагментов рисования, все-таки аспекты создания программы должны быть сбалансированы относительно различных рассмотрений. Если, предположим, требуется заменить только один символ или фигуру на экране или добавить акцент к букве и совершенно точно известно, что это не повлияет ни на какие другие изображения, то можно не пользоваться методом Invalidate() , а написать просто отдельную процедуру рисования.
В очень сложном приложении можно даже написать целый класс, который отвечает за рисование на экране. Несколько лет назад, когда MFC были стандартной технологией для приложений с интенсивным использованием GDI, MFC следовали этой модели с помощью класса C++ с именем СИмя_приложения>View , который отвечал за это. Однако даже в таком случае этот класс имел функцию-член OnDraw() , которая была создана, чтобы быть точкой входа для большинства запросов рисования.