як зупинити мерехтіння C # винних форм - -


76

У мене є програма, яка по суті нагадує нанесення фарби. Однак у моєї програми є деякі мерехтливі проблеми. У моєму коді є такий рядок (який повинен позбутися мерехтіння - але ні):

this.SetStyle(ControlStyles.AllPaintingInWmPaint 
| ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

мій код (за мінусом супер та підкласів для фігур такий:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Paint
{
    public partial class Paint : Form
    {
        private Point startPoint;
        private Point endPoint;
        private Rectangle rect = new Rectangle();
        private Int32 brushThickness = 0;
        private Boolean drawSPaint = false;
        private List<Shapes> listOfShapes = new List<Shapes>();
        private Color currentColor;
        private Color currentBoarderColor;
        private Boolean IsShapeRectangle = false;
        private Boolean IsShapeCircle = false;
        private Boolean IsShapeLine = false;

        public SPaint()
        {

            InitializeComponent();
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

            currentColor = Color.Red;
            currentBoarderColor = Color.DodgerBlue;
            IsShapeRectangle = true; 
        }

        private void panelArea_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = panelArea.CreateGraphics();

            if (drawSPaint == true)
            {

                Pen p = new Pen(Color.Blue);
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

                if (IsShapeRectangle == true)
                {
                    g.DrawRectangle(p, rect);
                }
                else if (IsShapeCircle == true)
                {
                    g.DrawEllipse(p, rect);
                }
                else if (IsShapeLine == true)
                {
                    g.DrawLine(p, startPoint, endPoint);
                }
            }
            foreach (Shapes shape in listOfShapes)
            {

                shape.Draw(g);

            }
        }

        private void panelArea_MouseDown(object sender, MouseEventArgs e)
        {

            startPoint.X = e.X;
            startPoint.Y = e.Y;

            drawSPaint = true;
        }

        private void panelArea_MouseMove(object sender, MouseEventArgs e)
        {


            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {

                if (e.X > startPoint.X)
                {
                    rect.X = startPoint.X;
                    rect.Width = e.X - startPoint.X;
                }
                else
                {
                    rect.X = e.X;
                    rect.Width = startPoint.X - e.X;
                }
                if (e.Y > startPoint.Y)
                {
                    rect.Y = startPoint.Y;
                    rect.Height = e.Y - startPoint.Y;
                }
                else
                {
                    rect.Y = e.Y;
                    rect.Height = startPoint.Y - e.Y;
                }


                panelArea.Invalidate();

            }

        }

        private void panelArea_MouseUp(object sender, MouseEventArgs e)
        {

            endPoint.X = e.X;
            endPoint.Y = e.Y;

            drawSPaint = false;

            if (rect.Width > 0 && rect.Height > 0)
            {
                if (IsShapeRectangle == true)
                {
                    listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeCircle == true)
                {
                    listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeLine == true)
                {
                    listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
                }

                panelArea.Invalidate();
            }
        }


        private void rectangleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = true;
            IsShapeCircle = false;
            IsShapeLine = false; 
        }

        private void ellipseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = false;
            IsShapeCircle = true;
            IsShapeLine = false; 
        }

        private void lineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeCircle = false;
            IsShapeRectangle = false;
            IsShapeLine = true; 
        }

        private void ThicknessLevel0_Click(object sender, EventArgs e)
        {
            brushThickness = 0; 
        }

        private void ThicknessLevel2_Click(object sender, EventArgs e)
        {
            brushThickness = 2; 
        }

        private void ThicknessLevel4_Click(object sender, EventArgs e)
        {
            brushThickness = 4; 
        }

        private void ThicknessLevel6_Click(object sender, EventArgs e)
        {
            brushThickness = 6; 
        }

        private void ThicknessLevel8_Click(object sender, EventArgs e)
        {
            brushThickness = 8; 
        }

        private void ThicknessLevel10_Click(object sender, EventArgs e)
        {
            brushThickness = 10; 
        }

        private void ThicknessLevel12_Click(object sender, EventArgs e)
        {
            brushThickness = 12; 
        }

        private void ThicknessLevel14_Click(object sender, EventArgs e)
        {
            brushThickness = 14; 
        }

        private void FillColour_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }

        private void button1_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentBoarderColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }


    }
}

Як зупинити мерехтіння?

* ОНОВЛЕННЯ: * Цей код насправді чудово працює, коли я малюю безпосередньо на формі. Однак, коли я намагаюся малювати на панелі, мерехтіння стає проблемою


7
Ви також встановили this.DoubleBuffered = true;?
Марк Гравелл

1
@ Marc Gravell, я щойно спробував додати в this.DoubleBuffered = true; і все ще
блимає,

Чи панель Area повна елементів керування? Недійсність працює рекурсивно, і тому може натиснути кожну дочірню контрольну панель в areaArea, щоб перефарбувати себе
Polity

@ Політизм ні, panelArea - це просто панель, яку я використовую, щоб малювати. Він не має елементів управління, хоча ..
BigBug

1
+1, чесно кажучи, функція SetStyle допомогла мені очистити мерехтіння. c # набагато кращий за c ++ !!!
Неофіл

Відповіді:


71

Для "більш чистого рішення" і для того, щоб продовжувати використовувати базову панель, ви можете просто використовувати Reflection для реалізації подвійної буферизації, додавши цей код до форми, яка містить панелі, в яких ви хочете намалювати

    typeof(Panel).InvokeMember("DoubleBuffered", 
    BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic, 
    null, DrawingPanel, new object[] { true });

Де "DrawingPanel" - це назва панелі, для якої потрібно зробити подвійну буферизацію.

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


3
Це чудове рішення! Дуже простий у впровадженні і працює точно так, як
хочеться

5
@musefan Я дуже рада, що допомогла тобі: DI рада, що рішення, опубліковане на 2 роки пізніше вихідного питання, все ще може допомогти людям! Ось чому переповнення стека так добре! :)
гадюка

1
Це все ще мерехтить для мене. Де я можу додати цей код? PS. у цій відповіді слід сказати "вам потрібно" використовувати System.Reflection 'вгорі ".
john ktejik

2
Це потрібно викликати для панелі, на якій ви хочете зупинити мерехтіння, перш ніж почати її використовувати. Ви можете викликати це у випадку завантаження вашої основної форми (або форми, на якій буде панель). Куди ти це кличеш?
гадюка

3
@RotaryHeart, будь ласка, майте на увазі, що ви не повинні використовувати це в методі фарбування, оскільки він буде запускатися кожного разу, коли елемент керування перефарбовується, а відбиття коштує дорого. Вам потрібно встановити це лише один раз для кожного елемента керування. Я не впевнений, що це те, що ви зробили, після прочитання вашого коментаря вийшло, ніби це могло бути вашим рішенням.
гадюка

64

Нарешті вирішили мерехтіння. Оскільки я малював на панелі замість форми, рядок коду нижче не вирішить мерехтіння:

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint | 
    ControlStyles.UserPaint | 
    ControlStyles.DoubleBuffer, 
    true);

SetStyle повинен мати тип "YourProject.YourProject" (або похідний від нього), отже, вам потрібно створити клас як такий (щоб ви могли використовувати MyPanel, який буде похідним від SPaint.SPaint і, отже, дозволити вам використовувати подвійну буферизацію безпосередньо для панель - а не форма):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SPaint; 

namespace YourProject
{
    public class MyPanel : System.Windows.Forms.Panel
    {
        public MyPanel()
        {
            this.SetStyle(
                System.Windows.Forms.ControlStyles.UserPaint | 
                System.Windows.Forms.ControlStyles.AllPaintingInWmPaint | 
                System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, 
                true);
        }
    }
}

Після того, як ви це зробите (хоча вам дійсно ніколи не слід редагувати код дизайнера, якщо ви по-справжньому не знаєте, що робите), вам доведеться відредагувати Form.Designer.cs. Усередині цього файлу ви знайдете такий код:

this.panelArea = new YourProject.MyPanel();

Наведений вище рядок потрібно змінити на:

this.panelArea = new MyPanel(); 

Після того, як я виконав ці кроки, моя програма фарби більше не мерехтить.

Для всіх, хто має таку ж проблему, проблема нарешті вирішена.

Насолоджуйтесь!


Але це більше не з’являється у дизайнера. У будь-якому випадку це виправити?
wingerse

@WingerSendon - щоб уникнути проблем із дизайнером, використовуйте рішення "коду", таке як viper , яке встановлює подвійну буферизацію під час виконання.
ToolmakerSteve

43

Скопіюйте та вставте це у свій проект

protected override CreateParams CreateParams
{
    get
    {
        CreateParams handleParam = base.CreateParams;
        handleParam.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED       
        return handleParam;
    }
}

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


8
Щоб зробити ваші відповіді більш корисними, додайте коротке пояснення, чому конкретна частина коду може допомогти відповісти на питання.
Kris

2
Схоже, це може бути джерелом , також розміщеним тут на SO ... основне пояснення, чому це працює, полягає в тому, що воно дозволяє подвійну буферизацію для всіх елементів керування від рівня форми вниз, інакше подвійну буферизацію потрібно ввімкнути окремо для кожен ... можливо, ви захочете налаштувати подвійну буферизацію після цього, оскільки подвійна подвійна буферизація може дати небажані побічні ефекти
u8it

чому це працює, а не прийнята відповідь?
aswzen

@Ivan 10+? Нічого собі: D
Моморо

16

У мене була та сама проблема. Я ніколи не зміг на 100% позбутися мерехтіння (див. Пункт 2), але я скористався цим

protected override void OnPaint(PaintEventArgs e) {}

так само, як

this.DoubleBuffered = true;

Головне питання мерехтіння - переконатися, що ви

  1. розписуйте речі в потрібному порядку!
  2. переконайтеся, що ваша функція малювання становить <приблизно 1/60 секунди

winforms викликає OnPaintметод кожного разу, коли форму потрібно перемальовувати. Існує безліч способів його скасування, зокрема переміщення курсору миші над формою може іноді викликати подію перемальовування.

І важлива примітка про те OnPaint, що ви не починаєте щоразу з нуля, а натомість починаєте з того місця, де були, якщо залиєте кольором тла заливку, ви, ймовірно, почнете мерехтіти.

Нарешті, ваш об'єкт gfx. Усередині OnPaintвам потрібно буде відтворити графічний об'єкт, але ТІЛЬКИ якщо розмір екрану змінився. відтворення об’єкта є дуже дорогим, і його потрібно утилізувати перед тим, як відтворити (збір сміття не на 100% обробляє його правильно, або, як стверджує документація). Я створив змінну класу

protected Graphics gfx = null;

а потім використовував його локально OnPaintприблизно так, але це було тому, що мені потрібно було використовувати об'єкт gfx в інших місцях мого класу. В іншому випадку НЕ РОБІТЬ ЦЬОГО. Якщо ви малюєте лише в OnPaint, будь ласка, використовуйте e.Graphics!!

// clean up old graphics object
gfx.Dispose();

// recreate graphics object (dont use e.Graphics, because we need to use it 
// in other functions)
gfx = this.CreateGraphics();

Сподіваюся, це допомагає.


це не вирішило моєї проблеми :( .. цей код насправді чудово працює, коли я малюю безпосередньо на формі. Однак, коли я намагаюся малювати на панелі, мерехтіння стає проблемою ...
BigBug

@BlueMonster, ніж спробувати панель.DoubleBuffered = true;
Бурімі

Я рекомендую створити дочірній об’єкт класу панелі, успадковуючи його від панелі, а потім перевизначити цю дитину protected override void OnPaint(PaintEventArgs e) {}. Новий об’єкт з’явиться у вашому списку елементів, щоб потрапити у вашу форму. Також ви можете вручну відредагувати код, щоб змінити тип посилання, якщо у вас вже створена панель і у вас немає простої можливості просто відтворити її.
омусама

Використовуючи e.Graphicsдопомагає, поряд з this.DoubleBuffered = true. Використовуючи створений Graphicsподвійний буфер, не тільки посилює ефект мерехтіння, але й робить Control(або Form) білим протягом більшої частини часу.
Meow Cat 2012,

4

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

Зробіть оригінальну панель, яка у вас є (panelArea), прозорою областю і покладіть її зверху на другу панель, яку ви, наприклад, називаєте panelDraw. Переконайтеся, що спереду є панель panelArea Я підбив це, і воно позбулося мерехтіння, але залишив фігуру, яку малювали, розмитою, так що це теж не повне рішення.

Прозору панель можна зробити, замінивши деякі дії фарби на вихідну панель:

public class ClearPanel : Panel
{
    public ClearPanel(){}

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams createParams = base.CreateParams;
            createParams.ExStyle |= 0x00000020;
            return createParams;
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e){}
}

Ідея полягає в тому, щоб обробити тимчасову фігуру під час події MouseMove "panelArea" і ТІЛЬКИ перефарбувати "panelDraw" на MouseUp Event.

// Use the panelDraw paint event to draw shapes that are done
void panelDraw_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelDraw.CreateGraphics();

    foreach (Rectangle shape in listOfShapes)
    {
        shape.Draw(g);
    }
}

// Use the panelArea_paint event to update the new shape-dragging...
private void panelArea_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelArea.CreateGraphics();

    if (drawSETPaint == true)
    {
        Pen p = new Pen(Color.Blue);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

        if (IsShapeRectangle == true)
        {
            g.DrawRectangle(p, rect);
        }
        else if (IsShapeCircle == true)
        {
            g.DrawEllipse(p, rect);
        }
        else if (IsShapeLine == true)
        {
            g.DrawLine(p, startPoint, endPoint);
        }
    }
}

private void panelArea_MouseUp(object sender, MouseEventArgs e)
{

    endPoint.X = e.X;
    endPoint.Y = e.Y;

    drawSETPaint = false;

    if (rect.Width > 0 && rect.Height > 0)
    {
        if (IsShapeRectangle == true)
        {
            listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeCircle == true)
        {
            listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeLine == true)
        {
            listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
        }

        panelArea.Invalidate();
    }

    panelDraw.Invalidate();
}

3

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


1
перечитавши ваше запитання, це, мабуть, не допоможе ... окрім як запропонувати, можливо, панель підкласів та дозволити вам перевизначити OnPaint та OnPaintBackground, і зробити там свій малюнок ...
Matt

3

Я знаю, це справді давнє запитання, але, можливо, комусь це стане в нагоді.
Я хотів би трохи покращити відповідь гадюки.

Ви можете зробити просте розширення до класу Panel і приховати властивість налаштування за допомогою відображення.

public static class MyExtensions {

    public static void SetDoubleBuffered(this Panel panel) {
        typeof(Panel).InvokeMember(
           "DoubleBuffered",
           BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
           null,
           panel,
           new object[] { true });
    }
}

Якщо ім'я змінної панелі - myPanel, ви можете просто зателефонувати
myPanel.SetDoubleBuffered ();
і це все. Код виглядає набагато чистішим.


3

У цьому стані вам потрібно ввімкнути подвійний буфер. Відкрийте поточну форму та перейдіть до властивостей форми та застосуйте подвійний буфер true; або ви також можете написати цей код.

this.DoubleBuffered = true;     

За формою навантаження.


1

ось програма рухомого кола в .net, яка не мерехтить.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
namespace CircleMove
{
    /// <summary>
    /// Description of MainForm.
    /// </summary>
    public partial class MainForm : Form
    {
        int x=0,y=0;
        Thread t;

        public MainForm()
        {

            //
            // The InitializeComponent() call is required for Windows Forms designer support.
            //
            InitializeComponent();

            //
            // TODO: Add constructor code after the InitializeComponent() call.
            //
        }
        void MainFormPaint(object sender, PaintEventArgs e)
        {
            Graphics g=e.Graphics;
            Pen p=new Pen(Color.Orange);
            Brush b=new SolidBrush(Color.Red);
        //  g.FillRectangle(b,0,0,100,100);
            g.FillEllipse(b,x,y,100,100);
        }
        void MainFormLoad(object sender, EventArgs e)
        {
            t=new Thread(  new ThreadStart(

                ()=>{
                    while(true)
                    {
                        Thread.Sleep(10);
                        x++;y++;
                        this.Invoke(new Action(
                            ()=>{

                                this.Refresh();
                                this.Invalidate();
                                this.DoubleBuffered=true;
                                }
                                            )
                                        );
                    }
                    }
                                            )

                        );

            t.Start();
        }
    }
}

1

якщо все вищезазначене не працює, ви завжди можете створити власне подвійне буферне посилання на підручник Microsofts: https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-reduce- графіка-мерехтіння-з-подвійною буферизацією для форм-і-елементів керування

сподіваюся, це працює для вас


1

Це трохи давнє запитання, але щоб бути повним: є ще одне рішення, яке спрацювало для мене там, де подвійної буферизації немає.

Як виявляється, Microsoft пропонує клас BufferedGraphics як рішення. Приємна річ цього класу полягає в тому, що він дозволяє копіювати один графічний об'єкт в інший, тому, крім налаштування тимчасового графічного об'єкта та врешті-решт копіювання його до кінцевого пункту призначення, можна використовувати майже той самий код, який можна було б зробити, коли мерехтіння не повинно було бути проблемою:

private void Indicator_Paint(object sender, PaintEventArgs e)
{
    Control pbIndicator = (Control)sender;
    Rectangle targetRect = pbIndicator.ClientRectangle;

    Image img = Bitmap.FromFile("bitmap.bmp");

    BufferedGraphicsContext ctx = new BufferedGraphicsContext();
    BufferedGraphics bg = ctx.Allocate(e.Graphics, targetRect);

    // Do the graphic stuff 
    bg.Graphics.Clear(this.BackColor);
    bg.Graphics.DrawImage(img, 0, 0);
    // etcetera

    bg.Render(e.Graphics);
    bg.Dispose();
    ctx.Dispose();
}

Недоліком цього рішення є те, що воно може захарастити ваш код. Крім того, я не впевнений, чи це гарна ідея встановлювати контекст кожного разу, чи було б достатньо створити його заздалегідь і продовжувати використовувати цей контекст.

Для отримання додаткової інформації див. Https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bufferedgraphicscontext?view=dotnet-plat-ext-3.1


0

Якщо пам’яті недостатньо (тому ви не хочете витрачати пам’ять на подвійну буферизацію), одним із можливих способів ЗМЕНШИТИ, хоча і не усунути мерехтіння, є встановлення кольору тла на домінуючий колір у вашій поточній сцені.

Чому це допомагає: мерехтіння - це миттєвий спалах кольору фону, який ОС малює перед тим, як намалювати дочірні елементи керування або власний код малювання. Якщо цей спалах є кольором, який ближче до остаточного кольору, що відображається, це буде менш помітно.

Якщо ви не впевнені, з якого кольору почати, почніть із 50% сірого, оскільки це середнє значення чорно-білого, тому буде ближче до більшості кольорів у вашій сцені.

myFormOrControl.BackColor = Color.Gray;

0

Спробуйте вставити логіку малювання в поточну форму

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
}

метод. У цьому випадку ви повинні використовувати параметр e, щоб отримати графічний об'єкт. Використовуйте властивість e.Graphics. Тоді ви повинні викликати метод Invalidate () для цієї форми, коли форму потрібно перемальовувати. PS: для DoubleBuffered має бути встановлено значення true.


0

Нанесення на етикетку замість панелі вирішило проблему для мене.

Не потрібно використовувати DoubleBuffering або щось інше.

Ви можете вилучити текст із мітки, встановити для параметра «Автоматичний розмір» значення «Помилка», а потім закріпити його або встановити «Розмір» і використовувати як для панелі.

Найкращі побажання,


Примітка: ArgumentException піднімається після виклику Dispose () для об’єкта Graphics із PaintEventArgs. Це трапляється, коли Мітка є першим (найвищим порядком) елементом керування в батьківському.
ThrowNewRandomException

-2

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

Або просто перевірте, чи миша опускається (за допомогою логічного значення, яке встановлює істину, коли миша не працює) за допомогою таймера, і розфарбуйте його, враховуючи, що ви, мабуть, намагаєтеся просто намалювати один піксель, а не як у вас є тінь тощо. Замість того, щоб використовувати фактичне введення. Отже, ви перевіряєте кожну 1 секунду замість 0,0001, і вона не мерехтить. Або навпаки, спробуйте зі свого часу.


1
Мерехтіння виникає, коли малюється фоновий колір (як правило, білий) форми або елемента керування, а потім ваш власний код запускається для перемальовування ураженої області. «Мерехтіння» - це миттєвий спалах кольору фону. Розв’язання мерехтіння вимагає подвійної буферизації, щоб система мала місце, з якого вона може отримати бажаний вміст для відновлення, а не малювати колір фону. Помістити код малювання на таймер гірше, ніж нічого не робити (витрачати цикли процесора без користі). Покладайтеся на ОС, яка скаже вам, коли вам потрібно намалювати, не намагайтеся перехитрити це.
ToolmakerSteve

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.