Wie Kacheln darstellen



  • Hallo,

    ich bin dabei ein C#-Programm zu schreiben
    welches große Bilder erzeugen und darstellen soll.
    Die Bilder können unter Umständen zu groß für den Arbeitsspeicher sein
    und müssen (aufwändig wäre übertrieben) berechnet werden.
    Deshalb habe ich mir überlegt Teilausschnitte ("Kacheln")
    mit je 256 x 256 Pixeln zu erzeugen (die lassen sich auch direkt berechnen).
    Es ist für mich kein Problem herauszufinden welche Kacheln zur Zeit
    sichtbar sind und welche hinzu und entfernt werden müssen.

    Ich weiß im Moment nur noch nicht, wie ich die Kacheln darstellen soll?
    Meine zwei Ideen bislang:

    • AutoScroll im übergeordneten Steuerelement und Kachel-Steuerelemente
      Damit habe ich schon experimentiert, jedoch funktioniert die Positionierung der Kacheln nicht.
    • AutoScroll im übergeordneten Steuerelement und selbst zeichnen
      (Doublebuffer deaktivieren, da ich die momentanen angezeigten Bilder Zwischenspeicher
      und somit einen eigenen Cache habe?)


  • Ein Panel mit Autoscroll sollte es doch tun. Und in den sichtbaren Bereich einfach die jeweiligen Kacheln laden.

    Ich habe mal eine Picturebox mit Zoomfunktion auf die Art gebaut und es funktioniert Prima. Gezeichnet wird per Hand über die Graphics-Klasse.



  • µ schrieb:

    Ein Panel mit Autoscroll sollte es doch tun. Und in den sichtbaren Bereich einfach die jeweiligen Kacheln laden.

    Damit habe ich ein Problem:

    bei mir sind die Kacheln immer verschoben:

    control.AutoScrollOffset = panel1.AutoScrollPosition;
                control.Location = new Point(selectedPosition.X * 256, selectedPosition.Y * 256);
    /// ....
                panel1.Controls.Add(control);
    

    funktioniert nicht, da immer noch verschoben.

    Disclaimer:
    Hiermit möchte ich mich schon mal im voraus für "funktioniert nicht" entschuldigen, da es bekanntlich keine gute Fehlerbeschreibung ist, jedoch ist dies mein bislang einziger Ansatz.



  • AutoScrollOffset ist glaube ich problematisch. Das ist sowieso sehr schlecht dokumentiert.

    Also ich hatte noch eine Picturebox in dem Panel (muss keine Picturebox sein, gezeichnet wurde ja sowieso "von Hand"). Bei einem Zoom wurde einfach die Picturebox vergrößert und die Scrollbars des Panel haben sich dann entsprechend angepasst. Die Position des sichtbaren Ausschnitts konnte über panel.AutoScrollPosition erechnet werden.

    Deine Kacheln haben doch eine feste Position, die vorher berechnet werden kann, oder?

    So hatte ich das sichtbare Rechteck bestimmt (Variante ohne Zoom, ansonsten alles durch Zoomfaktor teilen):

    private int vLeft { get { return (int)(-panel.AutoScrollPosition.X); } }
    private int vRight { get { return (int)(-panel.AutoScrollPosition.X + Math.Min(panel.ClientRectangle.Width, pBox.Width)); } }
    private int vTop { get { return (int)(-panel.AutoScrollPosition.Y); } }
    private int vBottom { get { return (int)(-panel.AutoScrollPosition.Y + Math.Min(panel.ClientRectangle.Height, pBox.Height)); } }
    
    public Rectangle ViewWin { get { return new Rectangle(vLeft, vTop, vRight - vLeft, vBottom - vTop); } }
    

    pBox existiert bei Dir nicht aber für eine Idee reicht es vielleicht.

    Dann müsstest Du nur noch berechnen welche Deiner Kacheln teilweise in diesem Rechteck liegt und entsprechend laden und anzeigen.



  • Quick & Dirty aber es funktioniert:

    doubleBufferedPanel.cs

    using System.Windows.Forms;
    
    namespace Kachel
    {
        public partial class doubleBufferedPanel : Panel
        {
            public doubleBufferedPanel()
            {
                SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
                this.DoubleBuffered = true;
            }
        }
    }
    

    Form1.Designer.cs

    namespace Kachel
    {
        partial class Form1
        {
            /// <summary>
            /// Erforderliche Designervariable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;
    
            /// <summary>
            /// Verwendete Ressourcen bereinigen.
            /// </summary>
            /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }
    
            #region Vom Windows Form-Designer generierter Code
    
            /// <summary>
            /// Erforderliche Methode für die Designerunterstützung.
            /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
            /// </summary>
            private void InitializeComponent()
            {
                this.panelOuter = new doubleBufferedPanel();
                this.panelInner = new doubleBufferedPanel();
                this.labelVisibleCounter = new System.Windows.Forms.Label();
                this.panelOuter.SuspendLayout();
                this.SuspendLayout();
                // 
                // panel1
                // 
                this.panelOuter.AutoScroll = true;
                this.panelOuter.Controls.Add(this.panelInner);
                this.panelOuter.Location = new System.Drawing.Point(47, 40);
                this.panelOuter.Name = "panel1";
                this.panelOuter.Size = new System.Drawing.Size(600, 400);
                this.panelOuter.TabIndex = 0;
                this.panelOuter.Scroll += new System.Windows.Forms.ScrollEventHandler(this.panelOuter_Scroll);
                // 
                // panel2
                // 
                this.panelInner.BackColor = System.Drawing.Color.WhiteSmoke;
                this.panelInner.Location = new System.Drawing.Point(0, 0);
                this.panelInner.Name = "panel2";
                this.panelInner.Size = new System.Drawing.Size(6000, 4000);
                this.panelInner.TabIndex = 0;
                // 
                // label1
                // 
                this.labelVisibleCounter.AutoSize = true;
                this.labelVisibleCounter.Location = new System.Drawing.Point(44, 9);
                this.labelVisibleCounter.Name = "label1";
                this.labelVisibleCounter.Size = new System.Drawing.Size(35, 13);
                this.labelVisibleCounter.TabIndex = 1;
                this.labelVisibleCounter.Text = "label1";
                // 
                // Form1
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.BackColor = System.Drawing.Color.Gainsboro;
                this.ClientSize = new System.Drawing.Size(702, 483);
                this.Controls.Add(this.labelVisibleCounter);
                this.Controls.Add(this.panelOuter);
                this.Name = "Form1";
                this.Text = "Form1";
                this.Load += new System.EventHandler(this.Form1_Load);
                this.panelOuter.ResumeLayout(false);
                this.ResumeLayout(false);
                this.PerformLayout();
    
            }
    
            #endregion
    
            private System.Windows.Forms.Panel panelOuter;
            private System.Windows.Forms.Panel panelInner;
            private System.Windows.Forms.Label labelVisibleCounter;
        }
    }
    

    Form1.cs

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace Kachel
    {
        public partial class Form1 : Form
        {
            List<PictureBox> kacheln = new List<PictureBox>();
            private int vLeft { get { return (int)(-panelOuter.AutoScrollPosition.X); } }
            private int vRight { get { return (int)(-panelOuter.AutoScrollPosition.X + panelOuter.ClientRectangle.Width); } }
            private int vTop { get { return (int)(-panelOuter.AutoScrollPosition.Y); } }
            private int vBottom { get { return (int)(-panelOuter.AutoScrollPosition.Y + panelOuter.ClientRectangle.Height); } }
            public Rectangle ViewWin { get { return new Rectangle(vLeft, vTop, vRight - vLeft, vBottom - vTop); } }
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                DoubleBuffered = true;
    
                Random rand = new Random();
                int cntX = 10;
                int cntY = 10;
                for (int i = 0; i < cntX; ++i)
                    for (int j = 0; j < cntY; ++j)
                    {
                        var pbox = new PictureBox();
                        pbox.Location = new Point(i * panelInner.Width / cntX, j * panelInner.Height / cntY);
                        pbox.Size = new Size(panelInner.Width / cntX, panelInner.Height / cntY);
                        pbox.BackColor = Color.FromArgb(rand.Next());
    
                        kacheln.Add(pbox);
                    }
    
                KachelnLaden();
            }
    
            private void KachelnLaden()
            {
                panelInner.Controls.Clear();
                Rectangle viewWin = ViewWin;
                var sichtbareKacheln = kacheln.Where(p => viewWin.IntersectsWith(new Rectangle(p.Location, p.Size))).ToList();
                panelInner.Controls.AddRange(sichtbareKacheln.ToArray());
                labelVisibleCounter.Text = string.Format("#sichtbare Kacheln: {0}", sichtbareKacheln.Count.ToString());
            }
    
            private void panelOuter_Scroll(object sender, ScrollEventArgs e)
            {
                KachelnLaden();
            }
        }
    }
    


  • @μ: Super danke:

    Durch rumpropieren bin ich jetzt schließlich auf:

    Label tile = new Label();
    
    tile.AutoScrollOffset = new Point(position.X * 256, position.Y * 256 );
    tile.Location = new Point(position.X * 256 + AutoScrollPosition.X, position.Y * 256 + AutoScrollPosition.Y);
    

    gekommen und es funktioniert.

    So mein Beispielcode (ein klein wenig komplizierter wegen Cache:

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Collections;
    
    namespace Test
    {
        public partial class Form1 : Form
        {
            private readonly PointCache<Label> cache;
    
            public Form1()
            {
                AutoScroll = true;
                AutoScrollMinSize = new System.Drawing.Size(10000, 10000);
    
                cache = new PointCache<Label>(ctor, dtor);
                cache.Enabled = true;
    
                InitializeComponent();
    
                Scroll += new ScrollEventHandler(FormScrolled);
                Resize += new EventHandler(FormResized);
                UpdateCache();
            }
    
            private void FormResized(object sender, EventArgs e)
            {
                UpdateCache();
            }
    
            private Point lastAutoScrollPosition = Point.Empty;
    
            private void FormScrolled(object sender, ScrollEventArgs e)
            {
                if (AutoScrollPosition != lastAutoScrollPosition)
                {
                    UpdateCache();
    
                    lastAutoScrollPosition = AutoScrollPosition;
                }
            }
    
            private void UpdateCache()
            {
                // Basd scho
                Size client = ClientSize;
                Point scroll = AutoScrollPosition;
                scroll = new Point(-scroll.X, -scroll.Y);
                int vx = scroll.X / 256, vy = scroll.Y / 256;
                Point offset = new Point(scroll.X % 256, scroll.Y % 256);
                int vw = (client.Width + offset.X + 255) / 256, vh = (client.Height + offset.Y + 255) / 256;
    
                Rectangle rect = new Rectangle(vx, vy, vw, vh);
                if (cache.Bounds != rect)
                {
                    Console.WriteLine("= {0} : {1} ; {2} : {3}", vx, vy, vw, vh);
    
                    cache.Bounds = rect;
                    Console.WriteLine("#{0}", Controls.Count);
                }
            }
    
            // Wird aufgerufen, wenn eine Kachel an diesem Punkt erstellt werden soll.
            Label ctor(Point position)
            {
                Label tile = new Label();
    
                tile.AutoScrollOffset = new Point(position.X * 256, position.Y * 256 );
                tile.Location = new Point(position.X * 256 + AutoScrollPosition.X, position.Y * 256 + AutoScrollPosition.Y);
    
                tile.BackColor = SystemColors.ControlDarkDark;
    
                tile.BorderStyle = BorderStyle.Fixed3D;
                tile.Text = position.ToString();
                tile.Size = new Size(256, 256);
                tile.TextAlign = ContentAlignment.MiddleCenter;
    
                Console.WriteLine("+ Key: " + position);
    
                Controls.Add(tile);
    
                return tile;
            }
    
            void dtor(Point p, Label l)
            {
                Console.WriteLine("- Key: " + p);
    
                Controls.Remove(l);
            }
    
        }
    
        public class PointCache<T> : IDisposable, IEnumerable<KeyValuePair<Point, T>>
        {
            private Func<Point, T> ctor;
            private Action<Point, T> dtor;
    
            private readonly IDictionary<Point, T> cache = new Dictionary<Point, T>();
    
            private Rectangle cacheBounds = Rectangle.Empty;
    
            private bool enabled;
    
            public PointCache(Func<Point, T> initializer, Action<Point, T> deinitializer)
            {
                if (initializer == null)
                    throw new ArgumentNullException("initializer");
                if (deinitializer == null)
                    throw new ArgumentNullException("deinitializer");
    
                ctor = initializer;
                dtor = deinitializer;
            }
    
            public Boolean Enabled
            {
                get
                {
                    return enabled;
                }
                set
                {
                    if (enabled != value)
                    {
                        enabled = value;
                        if (!enabled)
                            Clear();
                        else
                        {
                            // recalculate all Elements
                            Rectangle r = cacheBounds;
                            cacheBounds = Rectangle.Empty;
                            Bounds = r;
                        }
                    }
                }
            }
    
            private void CheckObjectDisposed()
            {
                if (ctor == null || dtor == null)
                    throw new ObjectDisposedException("RegionCache");
            }
    
            public Rectangle Bounds
            {
                get
                {
                    return cacheBounds;
                }
                set
                {
                    CheckObjectDisposed();
    
                    if (!Enabled)
                        return;
    
                    if (value != cacheBounds)
                    {
                        Rectangle i = Rectangle.Intersect(cacheBounds, value);
    
                        // Das ginge bestimmt besser
                        for (int y = cacheBounds.Top; y < cacheBounds.Bottom; y++)
                            for (int x = cacheBounds.Left; x < cacheBounds.Right; x++)
                            {
                                Point v = new Point(x, y);
                                if (!i.Contains(v))
                                {
                                    T result = cache[v];
    
                                    cache.Remove(v);
                                    dtor(v, result);
                                }
                            }
                        for (int y = value.Top; y < value.Bottom; y++)
                            for (int x = value.Left; x < value.Right; x++)
                            {
                                Point v = new Point(x, y);
                                if (!i.Contains(v))
                                {
                                    cache[v] = ctor(v);
                                }
                            }
                        cacheBounds = value;
                    }
                }
            }
    
            public T this[Point index]
            {
                get
                {
                    return Enabled ? cache[index] : default(T);
                }
            }
    
            public void Clear()
            {
                CheckObjectDisposed();
    
                foreach (var entry in cache)
                {
                    dtor(entry.Key, entry.Value);
                }
    
                cache.Clear();
                cacheBounds = Rectangle.Empty;
            }
    
            #region IDisposable Member
    
            public void Dispose()
            {
                Clear();
                Enabled = false;
                if (ctor != null)
                    ctor = null;
                if (dtor != null)
                    dtor = null;
            }
    
            #endregion
    
            #region IEnumerable<KeyValuePair<Vector2i,T>> Member
    
            public IEnumerator<KeyValuePair<Point, T>> GetEnumerator()
            {
                return cache.GetEnumerator();
            }
    
            #endregion
    
            #region IEnumerable Member
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                return cache.GetEnumerator();
            }
    
            #endregion
        }
    }
    

    (Dieses Projekt läuft als Konsolenanwendung, Console.WriteLine für Debugausgaben (finde ich ebsser als das Konsolenfenster von VS)
    falls Fragen bitte stellen, Verbesserungsvorschläge werden gerne genommen


Anmelden zum Antworten