Entwickler-Ecke

WinForms - Unterelemente nicht sichtbar, nach Hinzufügen per Code


Kasko - Di 28.07.20 14:37
Titel: Unterelemente nicht sichtbar, nach Hinzufügen per Code
Ich möchte eine einfache FileExplorer-Control für einen Videoplayer erstellen.

Hier ist der Code:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
namespace CustomControls
{
    public interface IScrollable
    {
        float visiblePercent { get; set; }
        float ScrollSpeedMultiplier { get; set; }
        int scrollValue { get; set; }

        CustomScrollBar scrollBar { get; set; }
    }
}


C#-Quelltext
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:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
namespace CustomControls {
    public partial class ScrollablePanel : UserControl, IScrollable {
        private int _scrollValue;
        private int _totalWheelDelta;

        private int totalWheelDelta {
            get => _totalWheelDelta;
            set {
                SetTotalDelta(value);
            }
        }

        public int Count => panelInner.Controls.Count;

        public bool VisibleControls => panelInner.Controls.OfType<Panel>().All(p => p.Visible);

        public float visiblePercent { get; set; }

        public int scrollValue {
            get { return _scrollValue; }
            set {
                _scrollValue = value;
                if (scrollBar != null && scrollBar.value != _scrollValue)
                    scrollBar.value = _scrollValue;
                SetTotalDeltaByScrollValue(value);
                ScrollThrough();
            }
        }

        [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), DefaultValue(0), Category("Behavior"), Description("Die dazugehörige Scrollbar")]
        public CustomScrollBar scrollBar { get; set; }

        public float ScrollSpeedMultiplier { get; set; }

        public ScrollablePanel() {
            InitializeComponent();
            panelInner.MouseWheel += panelInner_MouseWheel;
        }

        protected override ControlCollection CreateControlsInstance() {
            return new ScrollablePanelElementCollection(thisthis.panelInner);
        }

        private void ScrollablePanel_Load(object sender, EventArgs e) {
            OnControlsChanged();
        }

        private void panelInner_MouseWheel(object sender, MouseEventArgs e) {
            if (visiblePercent >= 1.0f)
                return;

            totalWheelDelta += (int)(e.Delta * ScrollSpeedMultiplier);

            scrollValue = (100 * SystemInformation.MouseWheelScrollLines * totalWheelDelta) / ((Height - panelInner.Height) * SystemData.scrollDeltaModifier);

            if (scrollBar != null)
                scrollValue = scrollBar.value = (100 * SystemInformation.MouseWheelScrollLines * totalWheelDelta) / ((Height - panelInner.Height) * SystemData.scrollDeltaModifier);
        }

        private void ScrollThrough() {
            panelInner.Location = new Point(0, (int)((Height - panelInner.Height) * (scrollValue / 100.0f)));
        }

        private void panelInner_ControlAdded(object sender, ControlEventArgs e) {
            OnControlsChanged();
        }

        private void panelInner_ControlRemoved(object sender, ControlEventArgs e) {
            OnControlsChanged();
        }

        private void OnControlsChanged() {
            int height = int.MinValue;

            foreach (Control control in panelInner.Controls)
                if (control.Visible)
                    height = Math.Max(height, control.Bottom);

            panelInner.Height = height;

            ScrollablePanel_Resize(thisnew EventArgs());

            if (panelInner.Height > 0)
                visiblePercent = (float)Height / panelInner.Height;
            else
                visiblePercent = 1.0f;

            if (scrollBar != null)
                scrollBar.largeChange = (int)(visiblePercent * 100);
        }

        private void ScrollablePanel_Resize(object sender, EventArgs e) {
            panelInner.Width = Width;
        }

        private void SetTotalDelta(int value) {
            int lines = SystemInformation.MouseWheelScrollLines * (value / SystemData.scrollDeltaModifier);
            int deltaHeight = Height - panelInner.Height;

            if (lines > 0)
                _totalWheelDelta = 0;
            else if (lines < deltaHeight)
                _totalWheelDelta = (deltaHeight * SystemData.scrollDeltaModifier) / SystemInformation.MouseWheelScrollLines;
            else
                _totalWheelDelta = value;
        }

        private void SetTotalDeltaByScrollValue(int value) {
            int deltaHeight = Height - panelInner.Height;

            totalWheelDelta = (deltaHeight * SystemData.scrollDeltaModifier * scrollValue) / (100 * SystemInformation.MouseWheelScrollLines);
        }

        public void Clear() {
            panelInner.Controls.Clear();
        }
    }

    public class ScrollablePanelElementCollection : Control.ControlCollection {
        private Control container;

        public ScrollablePanelElementCollection(Control owner, Panel container) : base(owner) { this.container = container; }

        public override void Add(Control value) {
            if (this.Count == 0)
                base.Add(value);
            else
                container.Controls.Add(value);
        }
    }
}



C#-Quelltext
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:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
namespace MagererPlayer {
    public partial class FileExplorer : UserControl, IScrollable {
        private string _directory;

        public float visiblePercent {
            get => scrollablePanel.visiblePercent;
            set => scrollablePanel.visiblePercent = value;
        }
        
        public int scrollValue {
            get => scrollablePanel.scrollValue;
            set => scrollablePanel.scrollValue = value;
        }
        
        public CustomScrollBar scrollBar {
            get => scrollablePanel.scrollBar;
            set => scrollablePanel.scrollBar = value;
        }

        public string Directory {
            get => this._directory;
            set {
                if (System.IO.Directory.Exists(value)) {
                    this._directory = value;
                    GetDirectory();
                    this.textBoxUrl.Text = this._directory;
                }
            }
        }

        public float ScrollSpeedMultiplier { get; set; }

        public EventHandler OnDirectoryChanged = null;
        public EventHandler<FileSelectedEventArgs> OnFileSelected = null;

        public FileExplorer() {
            InitializeComponent();
        }

        private void FileExplorer_Load(object sender, EventArgs e) {
            AdjustSize();
        }

        private void FileExplorer_Resize(object sender, EventArgs e) {
            AdjustSize();
        }

        private void AdjustSize() {

        }

        private void GetDirectory() {
            scrollablePanel.Clear();
            System.Diagnostics.Trace.WriteLine(this.Directory);

            int offset = System.IO.Directory.GetParent(this.Directory) != null ? 1 : 0;

            int colCount = scrollablePanel.Width / 145;
            string[] dirs = System.IO.Directory.GetDirectories(new string(this.Directory.Where(c => !char.IsControl(c)).ToArray()));
            Array.Sort(dirs);

            if (offset == 1) {
                DirectoryInfo parent = System.IO.Directory.GetParent(this.Directory);
                scrollablePanel.Controls.Add(GetExplorerItem(parent.FullName, ".."new Point(00)));
            }

            for (int i = 0; i < dirs.Length; i++)
                scrollablePanel.Controls.Add(GetExplorerItem(dirs[i], new DirectoryInfo(dirs[i]).Name, new Point(((i + offset) % colCount) * 145, (i + offset) / colCount * 115 + 10)));

            offset += dirs.Length;

            dirs = System.IO.Directory.GetFiles(new string(this.Directory.Where(c => !char.IsControl(c)).ToArray()), "*.mp4", SearchOption.TopDirectoryOnly);
            Array.Sort(dirs);

            for (int i = 0; i < dirs.Length; i++)
                scrollablePanel.Controls.Add(GetExplorerItem(dirs[i], new FileInfo(dirs[i]).Name, new Point(((i + offset) % colCount) * 145, (i + offset) / colCount * 115 + 10)));

            System.Diagnostics.Trace.WriteLine(scrollablePanel.Count);
            System.Diagnostics.Trace.WriteLine(scrollablePanel.VisibleControls);
            this.Invalidate();
        }

        private Panel GetExplorerItem(string path, string text, Point location) {
            BunifuImageButton bunifuImageButton = new BunifuImageButton();
            Label label = new Label();
            Panel panel = new Panel();

            if (System.IO.Directory.Exists(path)) {
                bunifuImageButton.Image = Properties.Resources.folder;
                bunifuImageButton.Click += delegate (object sender, EventArgs e) {
                    this.Directory = path;
                };
            } else if (System.IO.File.Exists(path)) {
                bunifuImageButton.Image = Properties.Resources.video_1;
            }
            
            bunifuImageButton.ImageActive = null;
            bunifuImageButton.Location = new System.Drawing.Point(100);
            bunifuImageButton.Name = "bunifuImageButton";
            bunifuImageButton.Size = new System.Drawing.Size(12570);
            bunifuImageButton.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
            bunifuImageButton.TabStop = false;
            bunifuImageButton.Zoom = 5;
            bunifuImageButton.Cursor = Cursors.Hand; 

            label.Font = new System.Drawing.Font("Century Gothic"9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
            label.ForeColor = System.Drawing.Color.FromArgb(224224224);
            label.Name = "label";
            label.Dock = DockStyle.Bottom;
            label.Size = new Size(14540);
            label.Text = text;
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.AutoEllipsis = true;

            panel.Controls.Add(bunifuImageButton);
            panel.Controls.Add(label);
            panel.Location = location;
            panel.Name = "panel";
            panel.Size = new System.Drawing.Size(145110);

            return panel;
        }

        private void textBoxUrl_KeyDown(object sender, KeyEventArgs e) {
            if (e.KeyCode == Keys.Enter)
                this.Directory = this.textBoxUrl.Text;
        }
    }

    public class FileSelectedEventArgs : EventArgs {
        public FileInfo File { get; }

        public FileSelectedEventArgs(string path) {
            File = new FileInfo(path);
        }

        public FileSelectedEventArgs(FileInfo info) {
            File = info;
        }
    }
}


Das ScrollablePanel enthält ein innerPanel, das nach oben und unten verschoben wird. Der FileExplorer enthält eine TextView für die URL, einen Button und ein ScrollablePanel.

Mein Problem ist, dass beim Ausführen der Anwendung alles einwandfrei funktioniert. Wenn ich zum übergeordneten Ordner gehe (WICHTIG, KEINE SUBDIR) und dann erneut den Ordner öffne, sind alle Steuerelemente, die vorhanden sein sollten, unsichtbar, wurden jedoch der ControlCollection des innerPanels hinzugefügt.

Dies ist das einzige Ordnerverzeichnis, in dem dieser Fehler auftritt: D:\Serien\Anime\Yu-Gi-Oh! Zexal

Ausgabe von System.Diagnostics aus FileExplorer.GetDirectory:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
D:\Serien\Anime\Yu-Gi-Oh! Zexal
3
True
D:\Serien\Anime
47
True
D:\Serien\Anime\Yu-Gi-Oh! Zexal
3
True

Screenshot bei Programmstart: https://ibb.co/2g2Fk1G
Screenshot nach erneut öffnen: https://ibb.co/J7zKp36

Moderiert von user profile iconTh69: Code-Tags hinzugefügt
Moderiert von user profile iconTh69: Titel geändert (war "Winforms: Controls not visible after added programmatically").


Th69 - Di 28.07.20 16:26

Hast du dir die relevanten Werte (Location, Visible) mal im Debugger angeschaut?
Durchläuft der Code auch beim 2. Mal den Code mit denselben Werten?

PS: Du solltest deinen Code mal aufräumen (der ist ziemlich unleserlich), z.B. überflüssige Namensbereiche entfernen (und stattdessen [tt]using[/tt]-Deklaration benutzen), Eigenschaften sollten mit einem Großbuchstaben beginnen, ...


Kasko - Di 28.07.20 16:34

Es sind immer die richtigen und die gleichen Werte.

Zum PS. Die Code-Schnipsel sind aus verschiedenen Projekten. CustomControls und MagererPlayer. CustomControls habe ich erstellt falls ich eigene Controls erstelle die halbwegs generisch nutzbar sind. Vllt kommt der FileExplorer auch eines Tages dort rein. ScrollablePanel ist schon uralt und wurde als eine der ersten Controls hinzugefügt. DAMALS habe ich mich nicht sonderlich um Namenskonventionen gekümmert. Deshalb sind unter anderem manche Properties klein geschrieben


Kasko - Di 28.07.20 22:42

Okay falsch. Nicht alle Werte sind gleich. Tatsächlich gibt die Visible-Property bei Programmstart false zurück, obwohl bei diesem Versuch die Controls ja sichtbar sind. Nach dem erneuten öffnen gibt sie dann true zurück, aber dann sind sie nicht sichtbar. Beim öffnen des übergeordneten Ordners gibt sie auch true zurück, da ist aber alles sichtbar.

Output: (Ohne den übergeordneten weil sonst zu viel)


Quelltext
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:
Url: D:\Serien\Anime\Yu-Gi-Oh! Zexal
Location: {X=0,Y=10}
Bounds: {X=0,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: False

Location: {X=145,Y=10}
Bounds: {X=145,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: False

Location: {X=290,Y=10}
Bounds: {X=290,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: False
CollectionCount: 3


Url: D:\Serien\Anime\Yu-Gi-Oh! Zexal
Location: {X=0,Y=10}
Bounds: {X=0,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: True

Location: {X=145,Y=10}
Bounds: {X=145,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: True

Location: {X=290,Y=10}
Bounds: {X=290,Y=10,Width=145,Height=110}
Size: {Width=145, Height=110}
Visible: True
CollectionCount: 3


Kasko - Mi 29.07.20 01:40

Okay Fehler gefunden.

Wenn ich den Ordner Ausgewählt habe, musste ich immer nach unten scrollen. Dadurch wurde das innerPanel nach oben geschoben (erste Zeile wurde abgeschnitten/verdeckt) nach dem löschen habe ich das panel nicht wieder zurück geschoben.

Habe Folgendes zur Methode ScrollablePanel.OnControlsChanged hinzugefügt:


C#-Quelltext
1:
2:
if (panelInner.Bottom < this.Height)
    panelInner.Location = new Point(panelInner.Location.X, panelInner.Location.Y + Math.Min(Math.Abs(panelInner.Location.Y), this.Height - panelInner.Bottom));