richTextBox
От: borga  
Дата: 06.09.19 05:57
Оценка:
Есть два параллельных потока.
Первый работает в BackgroundWorker записывает в richTextBox некие данные, назовем их логи (richTextBox1.AppendText).
А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

Вопрос:
Я допускаю что наступит такое время когда точно совпадет момент работы richTextBox1.AppendText и richTextBox1.Clear.
Возникнет ли при этом конфликт?
Или это предусмотрено логикой работы richTextBox и он сам этот конфликт разрулит?
Спасибо!
Re: richTextBox
От: Nonmanual Worker  
Дата: 06.09.19 06:13
Оценка: 3 (1)
Здравствуйте, borga, Вы писали:

B>Есть два параллельных потока.

B>Первый работает в BackgroundWorker записывает в richTextBox некие данные, назовем их логи (richTextBox1.AppendText).
B>А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

B>Вопрос:

B>Я допускаю что наступит такое время когда точно совпадет момент работы richTextBox1.AppendText и richTextBox1.Clear.
B>Возникнет ли при этом конфликт?
B>Или это предусмотрено логикой работы richTextBox и он сам этот конфликт разрулит?
B>Спасибо!

Самое простое и очевидное — использовать lock для richTextBox1.AppendText и richTextBox1.Clear.
Re: richTextBox
От: samius Япония http://sams-tricks.blogspot.com
Дата: 06.09.19 07:38
Оценка: 5 (2) +1
Здравствуйте, borga, Вы писали:

B>Есть два параллельных потока.

B>Первый работает в BackgroundWorker записывает в richTextBox некие данные, назовем их логи (richTextBox1.AppendText).
Не следует работать с элементами управления не из потока, в котором этот элемент управления создан. BackgroundWorker может готовить данные как угодно, но richTextBox1.AppendText должен быть вызван через цикл сообщений. Для этого можно использовать контекст синхронизации, взятый при инициализации родительского элемента управления.

B>А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

Этот таймер дает сигнал через цикл сообщений, потому Clear и AppendText не наступят одновременно. Железно.
Re[2]: richTextBox
От: borga  
Дата: 06.09.19 08:22
Оценка:
Здравствуйте, samius, Вы писали:

S>Не следует работать с элементами управления не из потока, в котором этот элемент управления создан. BackgroundWorker может готовить данные как угодно, но richTextBox1.AppendText должен быть вызван через цикл сообщений. Для этого можно использовать контекст синхронизации, взятый при инициализации родительского элемента управления.

Для меня это не совсем понятно.
Не могли бы подробней расписать.

B>>А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

S>Этот таймер дает сигнал через цикл сообщений, потому Clear и AppendText не наступят одновременно. Железно.
Это хорошо.
Re: richTextBox
От: Nonmanual Worker  
Дата: 06.09.19 08:39
Оценка:
Здравствуйте, borga, Вы писали:

B>Есть два параллельных потока.

B>Первый работает в BackgroundWorker записывает в richTextBox некие данные, назовем их логи (richTextBox1.AppendText).
B>А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

B>Вопрос:

B>Я допускаю что наступит такое время когда точно совпадет момент работы richTextBox1.AppendText и richTextBox1.Clear.
B>Возникнет ли при этом конфликт?
B>Или это предусмотрено логикой работы richTextBox и он сам этот конфликт разрулит?
B>Спасибо!
BackgroundWorker Progress и Finished события вызываются в контексте родителького потока, если вы делаете richTextBox1.AppendText там то синхронизации не требуется.
Timer.OnTimer вызывается из другого потока. Работать в winforms c gui из не gui потока нельзя, если сказать просто. Вам нужно вызвать richTextBox1.Clear из родительского потока.
Это можно сделать несколькими способами. Напрмер используя Task и SyncronizationContext. Но если вы на ты с BackgroundWorker и как работает task не знаете, то вам лучше не использовать таймер, а его логику перенести во второй BackgroundWorker и делать clear в Progress или Finished событиях, тогда ничего синхронизировать не нужно.
Re[3]: richTextBox
От: samius Япония http://sams-tricks.blogspot.com
Дата: 06.09.19 08:41
Оценка:
Здравствуйте, borga, Вы писали:

B>Здравствуйте, samius, Вы писали:


S>>Не следует работать с элементами управления не из потока, в котором этот элемент управления создан. BackgroundWorker может готовить данные как угодно, но richTextBox1.AppendText должен быть вызван через цикл сообщений. Для этого можно использовать контекст синхронизации, взятый при инициализации родительского элемента управления.

B>Для меня это не совсем понятно.
B>Не могли бы подробней расписать.
https://docs.microsoft.com/ru-ru/dotnet/framework/winforms/controls/how-to-implement-a-form-that-uses-a-background-operation
Re[2]: richTextBox
От: samius Япония http://sams-tricks.blogspot.com
Дата: 06.09.19 08:42
Оценка:
Здравствуйте, Nonmanual Worker, Вы писали:

NW>Timer.OnTimer вызывается из другого потока. Работать в winforms c gui из не gui потока нельзя, если сказать просто. Вам нужно вызвать richTextBox1.Clear из родительского потока.


В дотнете два таймера. Один — который системный, он придет из левого потока. Другой, который кидается на форму и работает исключительно через сообщения окна. System.Windows.Forms.Timer — это именно такой. Оконный.
Re: richTextBox
От: kov_serg Россия  
Дата: 06.09.19 16:31
Оценка: 3 (1)
Здравствуйте, borga, Вы писали:

B>Есть два параллельных потока.

...
B>Возникнет ли при этом конфликт?
B>Или это предусмотрено логикой работы richTextBox и он сам этот конфликт разрулит?
Нет. GUI строго однопоточное если надо из других потоков то InvokeRequired и BeginInvoke... либо poling.

ps: Когда-то давно использовал вот такой костль для WinForms может пригодиться.
  Example.cs
using System;
using System.Windows.Forms;
using TextUtils;

public class Form1 : Form {
    TextBoxTrace tracer;
    public Form1() {
        var tmr = new Timer();
        var tb = new TextBox();
        //var tb = new RichTextBox();
        SuspendLayout();
        tmr.Enabled = true;
        tmr.Interval = 1000;
        tmr.Tick += new System.EventHandler(this.timer1_Tick);
        tb.Dock = DockStyle.Fill;
        tb.Multiline = true;
        Text = tb.GetType().Name;
        ClientSize = new System.Drawing.Size(320,240);
        Controls.Add(tb);
        ResumeLayout(false);
        PerformLayout();
        tracer = TextBoxTrace.CreateDefault(tb);
    }
    protected override void Dispose(bool disposing) {
        if (tracer != null) { tracer.Dispose(); tracer = null; }
        base.Dispose(disposing);
    }
    void timer1_Tick(object sender,EventArgs e) {
        tracer.Trace("timer {0}\r\n",DateTime.Now.Ticks);
    }
}

static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
  TextUtils.cs
using System;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TextUtils {
    public class TextBoxUtils {
        int ss,sl,cp,sx,sy;
        [DllImport("user32")]
        static extern int GetCaretPos(out Point lpPoint);
        [DllImport("user32.dll")]
        static extern int GetScrollPos(IntPtr hWnd,int nBar);
        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd,int wMsg,int wParam,int lParam);
        const int WM_SETREDRAW = 0x000B;
        const int WM_VSCROLL = 0x0115;
        const int WM_HSCROLL = 0x0114;
        const int SB_THUMBPOSITION = 4;
        public void Save(TextBoxBase tb) {
            ss = tb.SelectionStart;
            sl = tb.SelectionLength;
            cp = ss;
            if (tb.Focused) {
                Point p; GetCaretPos(out p);
                cp=tb.GetCharIndexFromPosition(p);
                sx=GetScrollPos(tb.Handle,0);
                sy=GetScrollPos(tb.Handle,1);
                SendMessage(tb.Handle,WM_SETREDRAW,0,0);
            }
        }
        public void Restore(TextBoxBase tb) {
            if (cp > ss) tb.Select(ss,sl);
            else tb.Select(ss + sl,-sl);
            if (tb.Focused) {
                SendMessage(tb.Handle,WM_VSCROLL,SB_THUMBPOSITION+(sy<<16),0);
                SendMessage(tb.Handle,WM_HSCROLL,SB_THUMBPOSITION+(sx<<16),0);
                SendMessage(tb.Handle,WM_SETREDRAW,1,0);
                tb.Invalidate();
            }
        }
        public static void ReplaceText(TextBoxBase tb,String text) {
            var ss = new TextBoxUtils();
            try {
                ss.Save(tb);
                tb.Text = text;
            }
            finally {
                ss.Restore(tb);
            }
        }
        public static void AppendText(TextBoxBase tb,String text) {
            var ss = new TextBoxUtils();
            try {
                ss.Save(tb);
                if (ss.ss + ss.sl == tb.TextLength) { // expand selection
                    if (ss.sl > 0) ss.sl += text.Length;
                    if (ss.cp == tb.TextLength - 1) { // move caret
                        ss.cp += text.Length;
                        if (ss.sl == 0) ss.ss++;
                    }
                }
                tb.AppendText(text);
            }
            finally {
                ss.Restore(tb);
            }
        }
    }
    public interface ITrace {
        void Trace(string fmt,params object[] prm);
        void Clear();
    }
    public class TextBoxTrace : IDisposable, ITrace {
        TextBoxBase tb;
        StringBuilder sb;
        Timer tmr;
        object mutex = new object();
        volatile bool inside = false;
        volatile bool modified = false;
        volatile bool clear = false;
        public TextBoxTrace(TextBoxBase tb) {
            this.tb = tb;
            this.sb = new StringBuilder();
            init();
        }
        private static ToolStripMenuItem menu_add(ContextMenuStrip menu, string text,Keys keys,Action action) {
            var mi = new ToolStripMenuItem();
            mi.Text = text;
            mi.ShortcutKeys = Keys.Control | keys;
            mi.Click += (s,e) => action();
            menu.Items.Add(mi);
            return mi;
        }
        public static TextBoxTrace CreateDefault(TextBoxBase tb) {
            var res = new TextBoxTrace(tb);
            var charset = (byte)System.Windows.Forms.VisualStyles.TextMetricsCharacterSet.Russian;
            tb.Font = new System.Drawing.Font("Courier New",9.75F,FontStyle.Regular,GraphicsUnit.Point,charset);
            if (tb is TextBox) ((TextBox)tb).ScrollBars = ScrollBars.Both;
            if (tb is RichTextBox) ((RichTextBox)tb).ScrollBars = RichTextBoxScrollBars.Both;
            tb.Multiline = true;
            tb.WordWrap = false;
            var menu= new ContextMenuStrip();
            menu_add(menu,"Select all",Keys.A,() => tb.SelectAll());
            menu_add(menu,"Cut",Keys.X,() => tb.Cut());
            menu_add(menu,"Copy",Keys.C,() => tb.Copy());
            menu_add(menu,"Paste",Keys.V,() => tb.Paste());
            tb.ContextMenuStrip = menu;
            return res;
        }
        void init() {
            inside = false;
            tmr = new Timer();
            tmr.Interval = 20;
            tmr.Enabled = true;
            tmr.Tick += tmrHandler;
        }
        void done() {
            tmr.Enabled = false;
            tmr.Dispose();
            tmr = null;
            tb = null;
        }
        public void Dispose() {
            done();
        }
        void tmrHandler(object sender,EventArgs e) {
            if (inside || !modified) return;
            try {
                inside = true;
                updateTextBox();
            }
            finally {
                inside = false;
            }
        }
        void updateTextBox() {
            string text; bool cls = false;
            lock (mutex) {
                text = sb.ToString();
                sb.Length = 0;
                modified = false;
                if (clear) { clear = false; text = ""; cls = true; }
            }
            if (text.Length > 0) TextBoxUtils.AppendText(tb,text);
            if (cls) tb.Clear();
        }
        public void AppendText(string text) {
            if (text.Length > 0) lock (mutex) {
                sb.Append(text);
                modified = true;
            }
        }
        public void Clear() {
            if (!clear) lock (mutex) {
                clear = true;
            }
        }
        public void Trace(string fmt,params object[] prm) {
            var text = String.Format(System.Globalization.CultureInfo.InvariantCulture,fmt,prm);
            AppendText(text);
        }
    }
}
  RtfUtils.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.IO;

namespace TextUtils {
    public class RtfWriter : IDisposable {
        RichTextBox rt;
        RichTextBox dst;
        struct State {
            public Font font;
            public Color color;
        };
        Stack<State> stack;
        public RtfWriter(RichTextBox dst) {
            this.dst = dst;
            rt = new RichTextBox();
            if (dst != null) rt.Font = dst.Font;
            stack = new Stack<State>();
        }
        public Font TextFont { get { return rt.SelectionFont; } set { rt.SelectionFont = value; } }
        public Color TextColor { get { return rt.SelectionColor; } set { rt.SelectionColor = value; } }
        public float TextSize {
            get { return rt.SelectionFont.Size; }
            set {
                rt.SelectionFont = new Font(
                    rt.SelectionFont.FontFamily,
                    value,
                    rt.SelectionFont.Style,
                    rt.SelectionFont.Unit,
                    rt.SelectionFont.GdiCharSet,
                    rt.SelectionFont.GdiVerticalFont);
            }
        }
        public FontStyle TextStyle { get { return rt.SelectionFont.Style; } set { rt.SelectionFont = new Font(rt.SelectionFont,value); } }
        public void SetTextStyles(params FontStyle[] prm) {
            int res = 0;
            for (int i = 0; i < prm.Length; ++i) res |= (int)prm[i];
            TextStyle = (FontStyle)res;
        }
        public string TextFontName {
            get { return TextFont.Name; }
            set {
                rt.SelectionFont = new Font(
                    value,
                    rt.SelectionFont.Size,
                    rt.SelectionFont.Style,
                    rt.SelectionFont.Unit,
                    rt.SelectionFont.GdiCharSet,
                    rt.SelectionFont.GdiVerticalFont);
            }
        }
        public void Write(string text) { rt.SelectedText = text; rt.SelectionLength = 0; }
        public void WriteLn() { Write("\r\n"); }
        public void WriteLn(string text) { Write(text + "\r\n"); }
        public void Enter() {
            State state = new State();
            state.font = TextFont;
            state.color = TextColor;
            stack.Push(state);
        }
        public void Leave() {
            State state = stack.Pop();
            TextFont = state.font;
            TextColor = state.color;
        }
        public void Dispose() {
            var tbu = new TextBoxUtils();
            if (dst != null) {
                try {
                    tbu.Save(dst);
                    //dst.Rtf = rt.Rtf;
                    dst.SelectionLength = 0;
                    dst.SelectionStart = dst.TextLength;
                    dst.SelectedRtf = rt.Rtf;
                    dst.SelectionLength = 0;
                }
                finally {
                    tbu.Restore(dst);
                }
            }
            rt.Dispose(); rt = null;
        }
        public string GetRtf() { return rt.Rtf; }
        public void WriteRtf(string rtf) { rt.SelectedRtf = rtf; rt.SelectionLength = 0; }
        public void WriteImage(Image img) {
            string rtfimg;
            RtfUtils wrk = new RtfUtils(dst != null ? dst : rt);
#if false
            rtfimg=wrk.FromImage(img);
#else
            using (Bitmap bmp = new Bitmap(img.Width,img.Height,PixelFormat.Format24bppRgb)) {
                using (Graphics g2 = Graphics.FromImage(bmp)) {
                    using (Brush brush = new SolidBrush(dst != null ? dst.BackColor : rt.BackColor)) {
                        g2.FillRectangle(brush,0,0,img.Width,img.Height);
                        g2.DrawImage(img,new Rectangle(0,0,img.Width,img.Height));
                    }
                }
                rtfimg = wrk.FromImage(bmp);
            }
#endif
            Enter();
            WriteRtf(rtfimg);
            Leave();
        }
        public void WriteImage(string name,Type type) {
            string fullname = type.Namespace + "." + name;
#if false
            string[] names = type.Assembly.GetManifestResourceNames();
            names=names;
#endif
            using (Stream strm = type.Assembly.GetManifestResourceStream(fullname)) {
                using (Image img = Image.FromStream(strm)) { WriteImage(img); }
                strm.Close();
            }
        }
        public class RtfUtils {
            RichTextBox tb;
            public RtfUtils(RichTextBox tb) {
                this.tb = tb;
                using (Graphics g = tb.CreateGraphics()) {
                    xDpi = g.DpiX;
                    yDpi = g.DpiY;
                }
            }
            #region WinAPI GDI and GDI+
            private enum EmfToWmfBitsFlags {
                EmfToWmfBitsFlagsDefault = 0x00000000,
                EmfToWmfBitsFlagsEmbedEmf = 0x00000001,
                EmfToWmfBitsFlagsIncludePlaceable = 0x00000002,
                EmfToWmfBitsFlagsNoXORClip = 0x00000004
            };
            [DllImport("gdiplus.dll")]
            static extern uint GdipEmfToWmfBits(IntPtr hEmf,uint bufferSize,byte[] buffer,int mappingMode,EmfToWmfBitsFlags flags);
            [DllImport("gdi32.dll")]
            static extern void DeleteEnhMetaFile(IntPtr hEmf);
            const int HMM_PER_INCH = 2540;
            const int TWIPS_PER_INCH = 1440;
            const int MM_ANISOTROPIC = 8;
            const string RTF_HEADER = @"{\rtf1\ansi\ansicpg1252\deff0\deflang1033";
            string RTF_IMAGE_POST = @"}";
            #endregion
            float xDpi;
            float yDpi;
            string GetImagePrefix(Image image) {
                StringBuilder res = new StringBuilder();
                int picw = (int)Math.Round((image.Width / xDpi) * HMM_PER_INCH);
                int pich = (int)Math.Round((image.Height / yDpi) * HMM_PER_INCH);
                int picwgoal = (int)Math.Round((image.Width / xDpi) * TWIPS_PER_INCH);
                int pichgoal = (int)Math.Round((image.Height / yDpi) * TWIPS_PER_INCH);
                // REO_RESIZABLE
                res.Append(@"{\pict\wmetafile8");
                res.Append(@"\picw");
                res.Append(picw);
                res.Append(@"\pich");
                res.Append(pich);
                res.Append(@"\picwgoal");
                res.Append(picwgoal);
                res.Append(@"\pichgoal");
                res.Append(pichgoal);
                res.Append(" ");
                return res.ToString();
            }
            private string GetRtfImage(Image image) {
                StringBuilder res = null;
                MemoryStream stream = null;
                Graphics graphics = null;
                Metafile metaFile = null;
                try {
                    res = new StringBuilder();
                    stream = new MemoryStream();
                    using (graphics = tb.CreateGraphics()) {
                        IntPtr hdc = graphics.GetHdc(); try {
                            metaFile = new Metafile(stream,hdc);
                        }
                        finally {
                            graphics.ReleaseHdc(hdc);
                        }
                    }
                    using (graphics = Graphics.FromImage(metaFile)) {
                        graphics.DrawImage(image,new Rectangle(0,0,image.Width,image.Height));
                    }
                    IntPtr hEmf = metaFile.GetHenhmetafile(); try {
                        uint bufferSize = GdipEmfToWmfBits(hEmf,0,null,MM_ANISOTROPIC,EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
                        byte[] buffer = new byte[bufferSize];
                        uint convertedSize = GdipEmfToWmfBits(hEmf,bufferSize,buffer,MM_ANISOTROPIC,EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
                        for (int i = 0; i < buffer.Length; ++i) res.Append(String.Format("{0:X2}",buffer[i]));
                    }
                    finally {
                        DeleteEnhMetaFile(hEmf);
                    }
                    return res.ToString();
                }
                finally {
                    if (graphics != null) graphics.Dispose();
                    if (metaFile != null) metaFile.Dispose();
                    if (stream != null) stream.Close();
                }
            }
            public string FromImage(Image image) {
                StringBuilder res = new StringBuilder();
                res.Append(RTF_HEADER);
                res.Append(GetImagePrefix(image));
                res.Append(GetRtfImage(image));
                res.Append(RTF_IMAGE_POST);
                return res.ToString();
            }
        }
    }
}
Re: richTextBox
От: Mr.Delphist  
Дата: 16.09.19 15:39
Оценка: 3 (1)
Здравствуйте, borga, Вы писали:

B>Есть два параллельных потока.

B>Первый работает в BackgroundWorker записывает в richTextBox некие данные, назовем их логи (richTextBox1.AppendText).
B>А второй периодически(System.Windows.Forms.Timer) выгружает логи из richTextBox в файлы и очищает(richTextBox1.Clear).

Это настолько Delphi-smell, что с ног сшибает. Не надо так делать.
UI в этом сценарии должен быть только для показа каких-то снапшотов с логов. Вся основная работа — формирование массивов лог-записей, выгрузка их в файлы и очистка — должно делаться целиком в памяти, без привязки к конкретному контролу. В общем, MVC/MVVM/MVP — вот это вот всё.

Ну и до кучи, лучше взять готовый логгер-движок, который будет иметь нужное число писателей с требуемым поведением (на экран, в файл, в базу данных, в SysLog-сервер, и что там ещё надо).
Re[2]: richTextBox
От: borga  
Дата: 24.09.19 12:12
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Это настолько Delphi-smell, что с ног сшибает. Не надо так делать.

Да все начиналось как баловство.
Ха и сейчас называть это поделие из двух строк язык не поворачивается.
Но безусловно Спасибо!!
MD>UI в этом сценарии должен быть только для показа каких-то снапшотов с логов. Вся основная работа — формирование массивов лог-записей, выгрузка их в файлы и очистка — должно делаться целиком в памяти, без привязки к конкретному контролу. В общем, MVC/MVVM/MVP — вот это вот всё.
Пойду грызть.
MD>Ну и до кучи, лучше взять готовый логгер-движок, который будет иметь нужное число писателей с требуемым поведением (на экран, в файл, в базу данных, в SysLog-сервер, и что там ещё надо).
Думаю что это будет уже позже.
Но Спасибо за наводку!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.