Foro de elhacker.net

Programación => .NET (C#, VB.NET, ASP) => Mensaje iniciado por: d91 en 19 Diciembre 2015, 15:32 pm



Título: crear controles en tiempo de ejecucion
Publicado por: d91 en 19 Diciembre 2015, 15:32 pm
hola a todos, en una aplicación que estoy construyendo tengo un formulario para llenar los datos de un usuario, pero como característica adicional este usuario puede escoger entre 3 servicios, estos los tengo con checkboxs para que elija los que quiera, pero puede suceder que el administrador del sistema agregue un nuevo servicio y entonces sean 4 servicios, Es posible crear estos checkbox en tiempo de ejecución, para cuando en la base de datos exista un nuevo servicio aparezca en el formulario o simplemente debo crear otro formulario donde asigne los servicios uno a uno para no modificar la App en caso existan nuevos servicios disponibles? estoy trabajando VS for desktop...


Título: Re: crear controles en tiempo de ejecucion
Publicado por: El Benjo en 19 Diciembre 2015, 19:35 pm
Sí, es posible crear los controles dinámicamente, pero en este caso creo que sería mejor que en vez de checkboxes utilizaras un combobox, listbox o un componente similar y agregaras los servicios dinámicamente, según existan, en dicho componente.


Título: Re: crear controles en tiempo de ejecucion
Publicado por: mOrfiUs0 en 19 Diciembre 2015, 23:58 pm
Te pongo un ejemplo, tómalo como tal
Aquí creo y cargo los controles en tiempo de ejecución. Provienen de un DataSet:
Código:
            foreach (DataColumn s in dt.Columns.Cast<DataColumn>()
                .Where(x => x.Caption.StartsWith(prefixParam))
                .OrderBy(x => x.DataType.ToString())
                .ThenBy(x => x.Caption))
            {
                //var GenricTypeValue = asb[s.Name] == null ? null : Convert.ChangeType(asb[s.Name], s.PropertyType);
                lblGeneric = new Label();
                lblGeneric.TabStop = false;
                lblGeneric.Name = "lbl" + s.Caption.Substring(prefixParam.Length);
                lblGeneric.Text = lblGeneric.Name.Substring(iPrefixControl + 2);
                this.Controls.Add(lblGeneric);
                txtGeneric = new RichTextBox();
                txtGeneric.DetectUrls = true;
                txtGeneric.Multiline = false;
                if ("Pepe Juan WebOficial ".Contains(s.Caption + " "))
                    txtGeneric.Multiline = true;
                txtGeneric.TabIndex = txtCount++;
                txtGeneric.Name = "txt" + s.Caption.Substring(prefixParam.Length);
                txtGeneric.DataBindings.Add(new Binding("Text", dt, s.Caption));
                this.Controls.Add(txtGeneric);
            }

Y aquí les sitúo:

Código:
private void posCtrlsInTP(TabPage tbpGeneric, List<Label> listLabel, int tbpGenericMaxHeight, DataTable dt, int iTabPages)
        {
            //SetTabHeader(tbpGeneric, this.BackColor);
            Label lastLabel = new Label();
            int iLbl = 0;
            foreach (Label ctrlLabel in listLabel)
            {
                Control ctrlGeneric = this.Controls[this.Controls.IndexOf(ctrlLabel) + 1];
                ctrlLabel.Parent = tbpGeneric;
                ctrlLabel.Left = 2 * sepLeft;
                ctrlLabel.Height = (int)(3F * sepTop);
                ctrlLabel.Top = (lastLabel.Top == 0 ? 0 : sepTop) + lastLabel.Top + lastLabel.Height;
                ctrlLabel.TabStop = false;
                ctrlLabel.AutoSize = true;
                ((Label)ctrlLabel).TextAlign = ContentAlignment.MiddleCenter;
                iLbl++;
                if (iLbl > 3)
                    ctrlLabel.Left = ((tbpGeneric.Width / 2) + (tbpGeneric.Width / 8)) - ctrlLabel.Width - (2 * sepLeft);
                else
                    ctrlLabel.Left = (tbpGeneric.Width / 8) - ctrlLabel.Width - (2 * sepLeft);

                ctrlGeneric.Parent = tbpGeneric;
                ctrlGeneric.Width = defWidth;
                //ctrlGeneric.Left = (tbpGeneric.Width / iPosMiddle) - ctrlGeneric.Width - (3 * sepLeft);
                ctrlGeneric.Left = ctrlLabel.Left + ctrlLabel.Width + (2 * sepLeft);
                if ((iTabPages > 1) && (iTabPages < 5))
                {
                    ((RichTextBox)ctrlGeneric).Multiline = true;
                    ctrlGeneric.Height = tbpGeneric.Height - (3 * sepTop);
                    ctrlGeneric.Width = tbpGeneric.Width - (4 * sepLeft);
                    ctrlGeneric.Left = (sepLeft);
                    ctrlLabel.Visible = false;
                }
                else
                    ctrlGeneric.Height = ctrlLabel.Height + (1 * sepTop);
                ctrlGeneric.Top = ctrlLabel.Top - (sepTop / 3); //re-Top for correct Alignment

                ToolTip tt = new ToolTip();

                string sName = ctrlGeneric.Name.Substring(iPrefixControl + 2);
                string sTip = sName;// rmTT.GetString("TIP_" + sName.ToUpper());
                tt.ReshowDelay = 400;
                tt.AutoPopDelay = 20000;
                tt.UseFading = true;
                tt.IsBalloon = true;
                tt.ToolTipTitle = sName;
                tt.SetToolTip(ctrlGeneric, sTip);
                lastLabel = ctrlLabel;
                if (iLbl == 3)
                    lastLabel = new Label();
            }
            //calculate Maximum Height
            int tbpGenericHeight = lastLabel.Top + lastLabel.Height + (4 * sepTop);
            if (tbpGenericHeight > tbpGenericMaxHeight) tbpGeneric.Parent.Height = tbpGenericHeight;
        }

Con este resultado:
(http://picload.org/image/ipiwcwa/ooccosmosmhd.jpg)

Hace muchos años que cargo casi todos los controles en tiempo de ejecución. Si mal no recuerdo, VB3 tenía la limitación de no poder crear controles, el formulario debía tener uno y a partir de el creabas una matriz de controles. Algo que como verás es´ta superado hace tiempo.
Saludos!!


Título: Re: crear controles en tiempo de ejecucion
Publicado por: Eleкtro en 20 Diciembre 2015, 04:32 am
Buenas

Añadir checkboxes a la UI suena bastante feo, aunque de todas formas no puedo hacer una buena evaluación del problema y post-recomendación sin saber el propósito de esos checkboxes, pero sea como sea yo creo que te convendría mucho más utilizar un ListBox al que añadirle las "opciones" o entradas, establecerle al control el modo de selección múltiple, y actualizar las entradas del ListBox en consecuencia. De esta manera sería un mecanismo más eficiente y además desocuparía parte del espacio que llenarían tantos chekboxes de la interfáz gráfica.
( ya te dió la idea el compañero @El Benjo, pero quise profundizar un poco en algunos de los motivos por los que podrías preferir utilizarlo. )

Aparte, quiero hacer un pequeño matíz:
Los controles implementan la interfáz IDisposable, por lo tanto se debe mantener una referencia a los controles que se crean en tiempo de ejecución, y una vez ya no se necesiten, liberarlos (llamando al método Control.Dispose()), de lo contrario se convierte en una metodología expensiva para la aplicación, pudiendo producir un buen leak de memoria dependiendo de cuantas veces se repita el procedimiento dinámico de creación de controles.
( Lo comento por si acaso para que lo tengas en cuenta si prefieres crear controles. )

Esto es una mezcla del ejemplo que ha mostrado el usuario @mOrfiUs0 y lo que he mencionado (aunque como ya dije, yo utilizaría un listbox):

Código
  1. ' Si el orden de los controles te importa, entonces utiliza una colección de tipo [b]IList(of T)[/b].
  2. Private ctrls As ICollection(Of Control) = New HashSet(Of Control)
  3.  
  4. Private Sub CreateControls(ByRef ctrls As ICollection(Of Control), ByVal dt As DataTable)
  5.  
  6.    ctrls.Clear()
  7.  
  8.    For Each col As DataColumn In dt.Columns.Cast(Of DataColumn)()
  9.  
  10.        Dim ctrl As Control = New CheckBox
  11.        With ctrl
  12.            .Text = String.Empty
  13.            .Location = Point.Empty
  14.            ' etc...
  15.        End With
  16.        ctrls.Add(ctrl)
  17.  
  18.    Next
  19.  
  20.    With Me
  21.        .SuspendLayout()
  22.        .Controls.AddRange(ctrls.ToArray)
  23.        .ResumeLayout()
  24.    End With
  25.  
  26. End Sub

Saludos


Título: Re: crear controles en tiempo de ejecucion
Publicado por: mOrfiUs0 en 20 Diciembre 2015, 15:03 pm
Aparte, quiero hacer un pequeño matíz:
Los controles implementan la interfáz IDisposable, por lo tanto se debe mantener una referencia a los controles que se crean en tiempo de ejecución...
No es necesario, por lo menos en c# y entiendo que IML es común. Así estás duplicando la información.
Un simple for each Control in Formulario.Controls te da una referencia y por tanto no es necesario almacenar otro array.
Para todos los controles que no están heredados de la API nativa de Windows, es decir no tienen un hWnd, el tema se complica un poco más, pero creo que esta fuera del alcance de este post
Saludos!!


Título: Re: crear controles en tiempo de ejecucion
Publicado por: Eleкtro en 20 Diciembre 2015, 17:26 pm
No es necesario, por lo menos en c# y entiendo que IML es común. Así estás duplicando la información.

¿Por qué antes de contradecir a alguien no haces la prueba del algodón con un profiller de memoria para .Net que te muestre las fugas o leaks de memoria provocadas (y la relación a objetos, etc) antes de afirmar algo así?, las cosas se pueden decir de otra manera, parece que critiques por criticar o compitas por competir.

Simplemente dale un vistazo a la referencia del código fuente online de .Net Framework, a los controles built-in de WinForms, y si profundizas verás de lo que hablo...

  • http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,fa57e2a4f7bd434c
  • http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/NativeMethods.cs,85e8023899eb9d10

Cómo ya comenté, la class Control (y UserControl) implementa la interfáz IDisposable; pues obviamente los controles built-in de Winforms utilizan recursos administrados, cómo no administrados, por lo tanto se debe llamar al método implementado Control.Dispose() si o si.

Hay otras classes que implementan la interfáz IDisposable y no es estrictamente necesario llamar al método Dispose() (ej. la class Process), pero este no es el caso.

El caso en el que realmente no sería necesario llamar a Control.Dispose(), sería si el control se añadiese a una colección de controles administrada (class ControlCollection) y liberases el control padre de esa colección (por ejemplo, al añadir los controles a la colección de controles de un Form, y despues de eso, liberar el Form), en ese caso WinForms administraría automaticamente las llamadas al método Control.Dispose() por cada control de la colección, de lo contrario, no, y ese no es el caso, ya que aquí la pregunta trata sobre crear controles dinámica e indefinidamente cada vez que suceda "x" evento, y así... si no los liberas, el garbage-collector no los va a liberar por ti.



Un simple for each Control in Formulario.Controls te da una referencia y por tanto no es necesario almacenar otro array.

1. No conozco a ninguna otra persona que le resulte más "simple" escribir código repetitivo con un For, que declarar una referencia de nombre corto o shortname ref;
    Indiscutiblemente es más productivo que escribir el bloque de un For, y es más eficiente que irerar una colección de controles hasta encontrar dicho control.

2. No es duplicar.

Aquí tienes un simple ejemplo demostrativo siguiendo la linea del código que publiqué arriba:
Código
  1. Dim ctrls As ICollection(Of Control) = New HashSet(Of Control) From {
  2.    New Control
  3. }
  4.  
  5. Me.Controls.AddRange(ctrls.ToArray)
  6. Me.Controls(0).Dispose()
  7. MsgBox(ctrls(0).IsDisposed())

...Conviértelo a C# si quieres, estoy vago.



Para todos los controles que no están heredados de la API nativa de Windows, es decir no tienen un hWnd

Todo control (o user-control) es practicamente una ventana Win32, ya que implementa la interfáz IWin32Window, por lo tanto tienen un handle de ventana o hWnd, todos, ya que los controles de WinForms están basados en este modelo Win32.

Sin embargo, si estuviesemos hablando de WPF, la cosa sería distinta, pero obviamente tampoco es el caso. De todas formas de tecnología WPF si que se muy poquito, lo he usado muy poco.

Sin más, espero que esto haya sido suficiente con los motivos o argumentos que he dado. Si tienes preguntas al respecto, haz las que quieras (pero por favor, formula cualquier pregunta en un nuevo post, no invadamos más este hilo con offtopics).

Saludos.


Título: Re: crear controles en tiempo de ejecucion
Publicado por: mOrfiUs0 en 20 Diciembre 2015, 23:03 pm
vale