J'ai un listbox qui est lié à une collection d'objets. La zone de liste est configurée pour afficher une propriété d'identificateur de chaque objet. J'aimerais afficher une info-bulle contenant des informations spécifiques à l'élément de la zone de liste survolé au lieu d'une info-bulle pour la zone de liste dans son ensemble.
Je travaille au sein de WinForms et, grâce à quelques articles de blog utiles, j'ai trouvé une jolie solution que je voulais partager.
Je serais intéressé de voir s'il existe d'autres solutions élégantes à ce problème, ou comment cela peut être fait dans WPF.
Il existe deux principaux sous-problèmes qu’il faut résoudre pour résoudre ce problème:
Le premier problème est assez simple à résoudre. En appelant une méthode telle que la suivante dans votre gestionnaire pour MouseHover, vous pouvez déterminer quel élément est survolé:
private ITypeOfObjectsBoundToListBox DetermineHoveredItem()
{
Point screenPosition = ListBox.MousePosition;
Point listBoxClientAreaPosition = listBox.PointToClient(screenPosition);
int hoveredIndex = listBox.IndexFromPoint(listBoxClientAreaPosition);
if (hoveredIndex != -1)
{
return listBox.Items[hoveredIndex] as ITypeOfObjectsBoundToListBox;
}
else
{
return null;
}
}
Utilisez ensuite la valeur renvoyée pour définir l'info-bulle selon vos besoins.
Le deuxième problème est que normalement l'événement MouseHover n'est pas déclenché jusqu'à ce que le curseur ait quitté la zone cliente du contrôle et revienne ensuite.
Vous pouvez contourner ce problème en encapsulant l'appel TrackMouseEvent
Win32API.
Dans le code suivant, la méthode ResetMouseHover
encapsule l'appel API pour obtenir l'effet souhaité: réinitialisez le temporisateur sous-jacent qui contrôle le moment où l'événement survolé est déclenché.
public static class MouseInput
{
// TME_HOVER
// The caller wants hover notification. Notification is delivered as a
// WM_MOUSEHOVER message. If the caller requests hover tracking while
// hover tracking is already active, the hover timer will be reset.
private const int TME_HOVER = 0x1;
private struct TRACKMOUSEEVENT
{
// Size of the structure - calculated in the constructor
public int cbSize;
// value that we'll set to specify we want to start over Mouse Hover and get
// notification when the hover has happened
public int dwFlags;
// Handle to what's interested in the event
public IntPtr hwndTrack;
// How long it takes for a hover to occur
public int dwHoverTime;
// Setting things up specifically for a simple reset
public TRACKMOUSEEVENT(IntPtr hWnd)
{
this.cbSize = Marshal.SizeOf(typeof(TRACKMOUSEEVENT));
this.hwndTrack = hWnd;
this.dwHoverTime = SystemInformation.MouseHoverTime;
this.dwFlags = TME_HOVER;
}
}
// Declaration of the Win32API function
[DllImport("user32")]
private static extern bool TrackMouseEvent(ref TRACKMOUSEEVENT lpEventTrack);
public static void ResetMouseHover(IntPtr windowTrackingMouseHandle)
{
// Set up the parameter collection for the API call so that the appropriate
// control fires the event
TRACKMOUSEEVENT parameterBag = new TRACKMOUSEEVENT(windowTrackingMouseHandle);
// The actual API call
TrackMouseEvent(ref parameterBag);
}
}
Avec le wrapper en place, vous pouvez simplement appeler ResetMouseHover(listBox.Handle)
à la fin de votre gestionnaire MouseHover et l'événement survol se déclenchera à nouveau même si le curseur reste dans les limites du contrôle.
Je suis sûr que cette approche, coller tout le code dans le gestionnaire MouseHover doit entraîner plus d'actes MouseHover qu'il n'est vraiment nécessaire, mais le travail sera fait. Toutes les améliorations sont plus que bienvenues.
À l'aide de l'événement MouseMove, vous pouvez suivre l'index de l'élément survolé par la souris et le stocker dans une variable qui conserve sa valeur entre MouseMoves. Chaque fois que MouseMove est déclenché, il vérifie si l'index a changé. Si tel est le cas, il désactive l'info-bulle, modifie le texte de cette dernière pour ce contrôle, puis le réactive.
Vous trouverez ci-dessous un exemple dans lequel une propriété unique d'une classe Car est affichée dans un contrôle ListBox, mais des informations complètes sont ensuite affichées lorsque vous survolez une ligne. Pour que cet exemple fonctionne, il vous suffit d'un ListBox appelé lstCars avec un événement MouseMove et d'un composant texte ToolTip appelé tt1 sur votre WinForm.
Définition de la classe de voiture:
class Car
{
// Main properties:
public string Model { get; set; }
public string Make { get; set; }
public int InsuranceGroup { get; set; }
public string OwnerName { get; set; }
// Read only property combining all the other informaiton:
public string Info { get { return string.Format("{0} {1}\nOwner: {2}\nInsurance group: {3}", Make, Model, OwnerName, InsuranceGroup); } }
}
Evénement de chargement de formulaire:
private void Form1_Load(object sender, System.EventArgs e)
{
// Set up a list of cars:
List<Car> allCars = new List<Car>();
allCars.Add(new Car { Make = "Toyota", Model = "Yaris", InsuranceGroup = 6, OwnerName = "Joe Bloggs" });
allCars.Add(new Car { Make = "Mercedes", Model = "AMG", InsuranceGroup = 50, OwnerName = "Mr Rich" });
allCars.Add(new Car { Make = "Ford", Model = "Escort", InsuranceGroup = 10, OwnerName = "Fred Normal" });
// Attach the list of cars to the ListBox:
lstCars.DataSource = allCars;
lstCars.DisplayMember = "Model";
}
Le code de l’info-bulle (y compris la création de la variable de niveau de classe appelée hoveredIndex):
// Class variable to keep track of which row is currently selected:
int hoveredIndex = -1;
private void lstCars_MouseMove(object sender, MouseEventArgs e)
{
// See which row is currently under the mouse:
int newHoveredIndex = lstCars.IndexFromPoint(e.Location);
// If the row has changed since last moving the mouse:
if (hoveredIndex != newHoveredIndex)
{
// Change the variable for the next time we move the mouse:
hoveredIndex = newHoveredIndex;
// If over a row showing data (rather than blank space):
if (hoveredIndex > -1)
{
//Set tooltip text for the row now under the mouse:
tt1.Active = false;
tt1.SetToolTip(lstCars, ((Car)lstCars.Items[hoveredIndex]).Info);
tt1.Active = true;
}
}
}
Je pense que la meilleure option, puisque votre liaison de données à votre listbox avec des objets, serait d'utiliser un datatemplate. Donc, vous pourriez faire quelque chose comme ça:
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=TaskName}"
ToolTipService.ToolTip="{Binding Path=TaskName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Bien sûr, vous devez remplacer la liaison ItemsSource par la source de votre liaison, et les parties Path, par la propriété publique, des objets de la liste que vous souhaitez réellement afficher. Plus de détails disponibles sur msdn
En utilisant l'attribut title, nous pouvons définir une info-bulle pour chaque élément de la liste dans une zone de liste.
Boucle cela pour tous les éléments dans une zone de liste.
ListItem li = new ListItem("text","key");
li.Attributes.Add("title","tool tip text");
J'espère que cela t'aides.
En utilisant onmouseover
, vous pouvez parcourir chaque élément de la liste et afficher la ToolTip
onmouseover="doTooltipProd(event,'');
function doTooltipProd(e,tipObj)
{
Tooltip.init();
if ( typeof Tooltip == "undefined" || !Tooltip.ready ) {
return;
}
mCounter = 1;
for (m=1;m<=document.getElementById('lobProductId').length;m++) {
var mCurrent = document.getElementById('lobProductId').options[m];
if(mCurrent != null && mCurrent != "null") {
if (mCurrent.selected) {
mText = mCurrent.text;
Tooltip.show(e, mText);
}
}
}
}
Voici un style qui crée un groupe de boutons radio en utilisant un contrôle ListBox. Tout est lié à MVVM-ing. MyClass contient deux propriétés String: MyName et MyToolTip. Ceci affichera la liste des boutons radio incluant l'info-bulle appropriée. Ce Setter est particulièrement intéressant pour le Setter for ToolTip, ce qui en fait une solution entièrement Xaml.
Exemple d'utilisation:
ListBox Style = "{StaticResource radioListBox}" ItemsSource = "{Liaison MyClass}" SelectedValue = "{Liaison SelectedMyClass}" />
Style:
<Style x:Key="radioListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="5" />
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="Transparent">
<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}" Content="{Binding MyName}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ToolTip" Value="{Binding MyToolTip}" />
</Style>
</Setter.Value>
</Setter>
</Style>
Vous pouvez utiliser ce code simple qui utilise l'événement onMouseMove de ListBox dans WinForms:
private void ListBoxOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
var listbox = sender as ListBox;
if (listbox == null) return;
// set tool tip for listbox
var strTip = string.Empty;
var index = listbox.IndexFromPoint(mouseEventArgs.Location);
if ((index >= 0) && (index < listbox.Items.Count))
strTip = listbox.Items[index].ToString();
if (_toolTip.GetToolTip(listbox) != strTip)
{
_toolTip.SetToolTip(listbox, strTip);
}
}
Bien sûr, vous devrez initialiser l’objet ToolTip dans le constructeur ou une fonction init:
_toolTip = new ToolTip
{
AutoPopDelay = 5000,
InitialDelay = 1000,
ReshowDelay = 500,
ShowAlways = true
};
Prendre plaisir!