Je suis en train d'écrire un simple contrôle personnalisé de ListBox
dessiné par le propriétaire et de me taper la tête contre le mur en essayant de comprendre comment implémenter ce qui me semble être une fonction directe. Mon ListBox
personnalisé est censé fonctionner de manière similaire à la liste "Ajout/Suppression de programmes" dans Windows XP. Autrement dit, il affiche une liste d'éléments comme d'habitude, mais lorsque l'utilisateur sélectionne un élément dans la liste, un bouton cliquable doit apparaître à côté du texte de l'élément. Dans mon cas, j'essaie d'afficher un bouton "Importer" à côté de chaque article dans mon ListBox
. Afin de garder le ListBox
personnalisé encapsulé, j'essaye d'afficher le bouton en remplaçant la méthode ListBox.OnDrawItem
(ie le bouton fait conceptuellement partie de l'élément de la zone de liste), mais je n'arrive pas à le faire fonctionner tout à fait raison.Quelle est la meilleure façon de dessiner des contrôles "enfants" (tels que des boutons) dans chaque élément d'une boîte aux lettres WinForms?
Je dois noter que j'essaie d'utiliser un seul bouton pour l'ensemble ListBox
. Lorsque l'utilisateur sélectionne un élément dans la liste, la méthode OnDrawItem
ne fait que repositionner ce bouton unique pour qu'il apparaisse à côté de l'élément sélectionné. Je l'ai 99% de travail: le problème est maintenant que lorsque le ListBox
défile et que l'élément sélectionné sort de l'écran, le bouton est toujours dessiné à sa position précédente, de sorte qu'il dessine au-dessus du mauvais élément. Je suppose que c'est parce que Windows n'essaiera pas de redessiner l'élément sélectionné s'il est hors écran, et donc le code de repositionnement ne sera pas appelé.
Voici la version coupé vers le bas de ce que j'ai en ce moment:
public partial class EventListBox : ListBox
{
private Button _importButton;
public EventListBox()
{
InitializeComponent();
this.DrawMode = DrawMode.OwnerDrawVariable;
// set up the button that will appear next to the currently-selected item
_importButton = new Button();
_importButton.Text = "Import...";
_importButton.AutoSize = true;
_importButton.Visible = false;
// Add the button as a child control of this ListBox
Controls.Add(_importButton);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if (this.Items.Count > 0)
{
e.DrawBackground();
// draw item here (omitted)
// if drawing the selected item, re-position the "Import" button so that appears
// inside the current item, and make it visible if it is hidden.
// These checks prevent the resulting repaint that will occur from causing an infinite loop
// The problem seems to be that if the ListBox is scrolled such that the selected item
// moves off-screen , this code won't run, because it won't repaint the selected item anymore...
// This means the button will be painted in its previous position.
// The real question is: Is there a better way to approach the whole notion of
// rendering buttons within ListBox items?
if (e.State & DrawItemState.Selected == DrawItemState.Selected)
{
_importButton.Top = e.Bounds.Bottom - _importButton.Height - 20;
_importButton.Left = e.Bounds.Left;
if(!_importButton.Visible) _importButton.Visible = true;
}
}
base.OnDrawItem(e);
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
base.OnMeasureItem(e);
e.ItemHeight = 100; //hard-coded for now...
}
}
Justification de l'aide d'un seul bouton
Je préférerais créer un bouton distinct pour chaque élément dans la ListBox
, mais je ne trouve aucun moyen de savoir quand des éléments sont ajoutés/supprimés de la liste. Je n'ai trouvé aucune méthode pertinente ListBox
que je pourrais remplacer, et je ne peux pas réaffecter la propriété ListBox.Items
à un objet de collection personnalisé, puisque ListBox.Items
est en lecture seule. Pour cette raison, je suis allé avec l'approche ci-dessus d'utiliser un seul bouton et le repositionnement au besoin, mais comme je l'ai mentionné, ce n'est pas très robuste et se casse facilement. Je pense actuellement qu'il serait plus judicieux de créer un nouveau bouton au moment où de nouveaux éléments sont ajoutés au ListBox
, et de supprimer les boutons lorsque les éléments ont été supprimés.
Voici quelques solutions que j'ai trouvées, mais existe-t-il une meilleure façon de mettre en œuvre cela?
- je pouvais créer mes propres
AddItem
et méthodesRemoveItem
directement sur ma classe dérivéeListBox
. Je pourrais créer un bouton correspondant chaque fois qu'un nouvel article est ajouté, et enlever le bouton de l'article dansRemoveItem
. Cependant, pour moi, c'est un hack moche, car il me force à appeler ces méthodes spéciales d'ajout/suppression au lieu d'utiliser simplementListBox.Items
. - Je pourrais dessiner un nouveau bouton manuellement en
OnDrawItem
en utilisantSystem.Windows.Forms.ButtonRenderer
, mais alors je dois faire beaucoup de travail supplémentaire pour le faire agir comme un vrai bouton, puisqueButtonRenderer
ne fait rien de plus que dessiner le bouton. Déterminer quand l'utilisateur survole ce "bouton", et quand il est cliqué, semble qu'il serait difficile d'obtenir raison. - Lorsque
OnDrawItem
est appelée, je peux créer un nouveau bouton si l'élément en cours de dessin n'a pas encore de bouton associé (je pourrais garder une trace de ceci avec unDictionary<Item, Button>
), mais j'ai encore besoin d'un moyen de supprimer inutilisé boutons lorsque leurs éléments correspondants sont supprimés de la liste. Je suppose que je pourrais itérer sur mon dictionnaire de mappages de bouton d'article et supprimer des éléments qui n'existent plus dans leListBox
, mais alors je suis itérer sur deux listes chaque fois qu'un élément dans leListBox
est redessiné (ack!).
Alors, existe-t-il un meilleur moyen d'inclure des boutons cliquables à l'intérieur d'un ListBox
? C'est évidemment déjà fait, mais je ne trouve rien d'utile sur Google. Les seuls exemples que j'ai vus qui ont des boutons dans un ListBox
étaient des exemples WPF, mais je cherche comment faire avec WinForms.