I've got a ListBox that is displaying a dynamic number of TextBoxes. The user will enter text into these boxes. When the Submit button is clicked, I need to be able to access the text the user has input, should be at ListBox.Items, like so:
//Called on Submit button click
private void SaveAndSubmit(object sender, ExecutedRoutedEventArgs e)
{
var bounds = MyListBox.Items;
}
But MyListBox.Items doesn't change after I initially set the ItemsSource, here:
//Field declaration
//Bounds is containing a group of strings that represent the boundaries
//for a contour plot. The min/max values are stored at the front and back
//of the group. However, there can be any number of dividers in between.
public ObservableCollection<string> Bounds { get; set; }
...
//Initialize Bounds in the constructor
//Called when the selected item for DVList (an unrelated ListBox) is changed
private void DVSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedDV = DVList.SelectedItem as DVWrapper;
if (selectedDV != null)
{
//Setting min/max
Bounds[0] = selectedDV.MinValue;
Bounds[Bounds.Count - 1] = selectedDV.MaxValue;
MyListBox.ItemsSource = Bounds;
}
}
My XAML looks like this:
<Window.Resources>
<Style x:Key="BoundsStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
...
<TextBox/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Focusable" Value="False"/>
</Style>
</Window.Resources>
...
<ListBox Name="MyListBox"
ItemContainerStyle="{StaticResource BoundsStyle}"/>
So when SaveAndSubmit is called, bounds ends up being what I had originally set it to in DVSelectionChanged. In other words, the listbox is not updating based on what the user has input into the textboxes contained in listbox. How can I get the updated ListBoxItems? I think my problem is similar to this one, but it's not working for me at the moment.
When I step through in the debugger, I can get individual ListBoxItems. However, their Content is empty. I'm looking into that right now.
You need to bind content of the textbox.
<TextBox/> need to change to <TextBox Content="{Binding}"/>
But follow MVVM else it will be difficult to find these errors.
Related
I have A WPF Datagrid that has a Collection View Source with 3 levels of grouping on it.
I have styled the datagrid to use 3 expanders such that it looks like this:
Level 1 Expander
<content>
Level 2 Expander
<content>
Level 3 Expander
<content>
Level 2 and Level 1 are just title of the groups
I have a second control that allows the user to show and hide level 3 items which works by binding the Level 3 expander to a Boolean "IsVisible" property in the object behind.
<!-- Style for groups under the top level. this is the style for how a sample is displayed -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<!-- The parent control that determines whether or not an item needs to be displayed. This holds all of the sub controls displayed for a sample -->
<Expander Margin="2"
Background="{Binding Path=Name,
Converter={StaticResource SampleTypeToColourConverter}}"
IsExpanded="True"
Visibility="{Binding Path=Items[0].IsVisibleInMainScreen,
Converter={StaticResource BoolToVisibilityConverter}}">
This approach works fantasically well.
HOWEVER
If the user deselects all items in a level 3 expander, the Level 2 expander header still displays meaning that valuable real estate is used up showing the header of a group with no visible data.
What I would like is a way to bind the visibility of the level 2 expander to its child controls and say "If all children are visible then show the expander, otherwise collapse it"
Is this possible?
I found a rather simple and clean way, yet not perfect, to achieve your goal. This should do the trick if hou don't have too much groups.
I've just added this trigger to the GroupItem ControlTemplate :
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=IP, Path=ActualHeight}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
<Setter Property="Height" Value="1"/>
</DataTrigger>
</ControlTemplate.Triggers>
When the ItemsPresenter (IP) ActualSize drops to zero, it Will almost collapse the header.
Why almost ?
When the control gets initialized and before the binding occurs, the ItemPresenter ActualHeight is 0 and when Visibility is set to Collapsed, the ItemPresenter doesn't get rendered at all.
Using Visibility.Hidden allows the ItemsPresenter to go to the render phase and be mesured.
I succedeed to drop Height to .4 px but I suspect this to be device dependant.
Assuming that you are using an MVVM sort of style, you could bind instead to a property of your group object that returns false if all of the children are invisible:
public bool AreChildrenVisible { get { return _children.Any(x=>x.IsVisibleInMainScreen); } }
Alternatively, pass the collection of Items through a Converter class to return Visibility depending on the aggregate status of all the subItems in the group.
This isn't a direct answer as you would have to implement it specifically for your needs but previously I have used a an override of the Grid Control to create dynamic grid allocation of members, if there are no visible members it then hides the parent group box.
public class DynamicLayoutGrid : Grid
{
protected override void OnInitialized(EventArgs e)
{
//Hook up the loaded event (this is used because it fires after the visibility binding has occurred)
this.Loaded += new RoutedEventHandler(DynamicLayoutGrid_Loaded);
base.OnInitialized(e);
}
void DynamicLayoutGrid_Loaded(object sender, RoutedEventArgs e)
{
int numberOfColumns = ColumnDefinitions.Count;
int columnSpan = 0;
int rowNum = 0;
int columnNum = 0;
int visibleCount = 0;
foreach (UIElement child in Children)
{
//We only want to layout visible items in the grid
if (child.Visibility != Visibility.Visible)
{
continue;
}
else
{
visibleCount++;
}
//Get the column span of the element if it is not in column 0 as we might need to take this into account
columnSpan = Grid.GetColumnSpan(child);
//set the Grid row of the element
Grid.SetRow(child, rowNum);
//set the grid column of the element (and shift it along if the previous element on this row had a rowspan greater than 0
Grid.SetColumn(child, columnNum);
//If there isn't any columnspan then just move to the next column normally
if (columnSpan == 0)
{
columnSpan = 1;
}
//Move to the next available column
columnNum += columnSpan;
//Move to the next row and start the columns again
if (columnNum >= numberOfColumns)
{
rowNum++;
columnNum = 0;
}
}
if (visibleCount == 0)
{
if (this.Parent.GetType() == typeof(GroupBox))
{
(this.Parent as GroupBox).Visibility = Visibility.Collapsed;
}
}
}
}
Use IMultiValueConverter implementation to convert items to visibility.
If all items IsVisibleInMainScreen property return true the converter will return visible else hidden.
Use the converter in the same place U used to convert the first item in original example
I have a collection which I want to place on a grid. The collections items have properties that refer to where on the gird the item should be placed, and how many columns and rows the item should span. So the item looks sort of like this:
public class Item
{
public int FromRow { get; set; }
public int SpanRow { get; set; }
public int FromColumn { get; set; }
public int SpanColumn { get; set; }
}
I want the grid to generate the right number of columns and rows dynamically for the collection, and display the collection trough data templates for each item in it. I believe I have solved this part of the problem like so:
<ItemsControl ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ItemCollection">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid x:Name="Grid"
ShowGridLines="True"
dataHelper:DynamicGrid.RowCount="7"
dataHelper:DynamicGrid.ColumnCount="{Binding ColumnCount}"
dataHelper:DynamicGrid.ColumnWidth="{Binding ColumnWidth}">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Style.Setters>
<Setter Property="Grid.Row"
Value="{Binding FromRow}" />
<Setter Property="Grid.Column"
Value="{Binding FromColumn}" />
<Setter Property="Grid.RowSpan"
Value="{Binding SpanRow}" />
<Setter Property="Grid.ColumnSpan"
Value="{Binding SpanColumn}" />
</Style.Setters>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
As you can see I am using attached properties for the dynamic grid part. But there are two problems i cant figure out.
First problem: I want to enable drag and drop on the items populating the grid so that I can manipulate the items position on the grid. Now I need the mouse position where the drop is supposed to accure. I searched around and found this:
private void OnMouseMove(object sender, MouseEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
Which means that the grid must have UIElements in every cell, but the ItemsControl wont allow me to add anything to the grid which is understandable.
Second problem: I want the rows and columns to have iterative headers. What I meen by that is that column one has '1' in its first cell, column two has '2' in its first cell, and so on. The same goes for rows.
I hope I have explained my problems so you guys understand them. Any input would be much appreciated. And if you know a beter way to do what I have explained, other tools than a normal Grid in a ItemsControl, please share! :D
Thanks in advance!
I have an editor (textbox) and I'd like to bind different texts to it based on which button is clicked.
I could use a command on the button and pass the string which I'd like to edit through commandparameter and update the string which is bound to the textbox. This will work but it's not going to save the modifications because there is no binding between the text (which was passed through the commandparameter) and the textbox's text.
My question is, how should I implement this bind neatly without accessing the textbox directly from the View Model?
edit: it's probably vague what I'm trying to achieve. I try to clarify it a bit:
So let's say I have several different buttons, if I click one of those, it should bind some string to the editor's textbox, where I can modify it and save it later.
<Button Content="Edit query" Command="{Binding ShowQueryInEditorCommand}" CommandParameter="{Binding SomeSqlStringToBeEdited}"/>
<Button Content="Edit query" Command="{Binding ShowQueryInEditorCommand}" CommandParameter="{Binding SomeOtherSqlStringToBeEdited}"/>
This is what the command will execute:
public void ShowQueryInEditor(object o)
{
string SqlStatementParam = o as string;
if (SqlStatementParam != null)
SQLStatement = SqlStatementParam;
}
And the editor TextBox itself:
<TextBox Text="{Binding SQLStatement}">
As you can see, this is very rudimentary as it just sets the SQLStatement string, but there is no bind between them so it cannot reflect the modifications back to SomeSqlStringToBeEdited/SomeOtherSqlStringToBeEdited. This is what I would like to achieve, to bind that string somehow to the textbox when the button is clicked.
There are two basic ways I can think of: through code, or through Xaml.
In code, instead of accessing the textbox from the ViewModel, add a new property to the ViewModel for the "DisplayText" or "SelectedText", or whatever makes sense in your scenario. Bind your textbox to that property instead, and then put the rest of the logic you need inside the setter (or, if it's a DependencyProperty, the OnPropertyChanged callback). That keeps all the logic in your ViewModel and means the Xaml doesn't have to care.
Or in Xaml, you could use triggers and templates to change the textbox depending on the selected button. Most likely form your description, I would suggest having multiple textboxes, one bound to each string, and switch the visible textbox based on the button that's clicked. This keeps your ViewModel ignorant of this display-specific logic, and allows you to change it more easily later on.
Personally, I would likely suggest the Xaml approach, but it will depend on your specific situation.
According to
but the problem is that the buttons are created dynamically
1) Wrap you query text and button into view model like this:
public class ViewModel : ViewModelBase
{
public ViewModel()
{
this.turnIsSelectedOnCommand = new RelayCommand(() => IsSelected = true);
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
OnPropertyChanged("Text");
}
}
}
private String text;
public Boolean IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private Boolean isSelected;
public RelayCommand TurnIsSelectedOnCommand
{
get { return turnIsSelectedOnCommand; }
}
private readonly RelayCommand turnIsSelectedOnCommand;
}
2) Put your dynamically created text/buttons into collection. For simplicity, I've added them to array:
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new ViewModel { Text = "SELECT * FROM Foo", IsSelected = true },
new ViewModel { Text = "SELECT * FROM Bar" },
new ViewModel { Text = "DROP TABLE Foo" },
new ViewModel { Text = "DROP TABLE Bar" },
};
}
3) Bind the collection with ListBox, and the editor - with the Text of the selected item:
<ListBox Grid.Row="0" Margin="5" ItemsSource="{Binding}" x:Name="lbItems">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Right"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent"/>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="Edit query" Command="{Binding TurnIsSelectedOnCommand}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Row="1" Margin="5" Text="{Binding Path=SelectedItem.Text, ElementName=lbItems}" />
I've added some style modifications.
First modifies buttons layout.
Second means, that when you will push the button and ViewModel will become selected, list box item will be selected too.
Third hides selection from the background of the selected item.
Hope, this helps.
I'm trying to use the context menu in a listview to run some code that requires data from which item it originated from.
I initially just did this:
XAML:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Resources>
<ContextMenu x:Key="resourceContextMenu">
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
</Style>
</ListView.ItemContainerStyle>
...
C#:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
// code that needs item data here
}
But I found that the originating listview item was not accessible that way.
I've read some tactics about how to get around this, like intercepting the MouseDown event and setting a private field to the listviewitem that was clicked, but that doesn't sit well with me as it seems a bit hacky to pass data around that way. And WPF is supposed to be easy, right? :) I've read this SO question and this MSDN forum question, but I'm still not sure how to really do this, as neither of those articles seem to work in my case. Is there a better way to pass the item that was clicked on through to the context menu?
Thanks!
Similar to Charlie's answer, but shouldn't require XAML changes.
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}
Well in the cmMetadata_Click handler, you can just query the lvResources.SelectedItem property, since lvResources will be accessible from the code-behind file that the click handler is located in. It's not elegant, but it will work.
If you want to be a little more elegant, you could change where you set up your ContextMenu. For example, you could try something like this:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.Style>
<Style TargetType="ListView">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<TextBlock Text="{TemplateBinding Content}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</ListView.Style>
<ListViewItem>One Item</ListViewItem>
<ListViewItem>Another item</ListViewItem>
</ListView>
What this does is plug in a template for your ListViewItem, and then you can use the handy TemplatedParent shortcut to assign the ListViewItem to the DataContext of your menu item.
Now your code-behind looks like this:
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
MenuItem menu = sender as MenuItem;
ListViewItem item = menu.DataContext as ListViewItem;
}
Obviously the downside is you will now need to complete the template for a ListViewItem, but I'm sure you can find one that will suit your needs pretty quickly.
So I decided to try and implement a command solution. I'm pretty pleased with how it's working now.
First, created my command:
public static class CustomCommands
{
public static RoutedCommand DisplayMetadata = new RoutedCommand();
}
Next in my custom listview control, I added a new command binding to the constructor:
public SortableListView()
{
CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}
And also there, added the event handlers:
public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
var nbSelectedItem = (MyItem)e.Parameter;
// do stuff with selected item
}
public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
I was already using a style selector to dynamically assign styles to the listview items, so instead of doing this in the xaml, I have to set the binding in the codebehind. You could do it in the xaml as well though:
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
MyItem selectedItem = (MyItem)item;
Style s = new Style();
var listMenuItems = new List<MenuItem>();
var mi = new MenuItem();
mi.Header= "Get Metadata";
mi.Name= "cmMetadata";
mi.Command = CustomCommands.DisplayMetadata;
mi.CommandParameter = selectedItem;
listMenuItems.Add(mi);
ContextMenu cm = new ContextMenu();
cm.ItemsSource = listMenuItems;
// Global styles
s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));
// other style selection code
return s;
}
I like the feel of this solution much better than attempting to set a field on mouse click and try to access what was clicked that way.
I have a listbox that is databound to a Collection of objects. The listbox is configured to display an identifier property of each object. I would like to show a tooltip with information specific to the item within the listbox that is being hovered over rather than one tooltip for the listbox as a whole.
I am working within WinForms and thanks to some helpful blog posts put together a pretty nice solution, which I wanted to share.
I'd be interested in seeing if there's any other elegant solutions to this problem, or how this may be done in WPF.
There are two main sub-problems one must solve in order to solve this problem:
Determine which item is being hovered over
Get the MouseHover event to fire when the user has hovered over one item, then moved the cursor within the listbox and hovered over another item.
The first problem is rather simple to solve. By calling a method like the following within your handler for MouseHover, you can determine which item is being hovered over:
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;
}
}
Then use the returned value to set the tool-tip as needed.
The second problem is that normally the MouseHover event isn't fired again until the cursor has left the client area of the control and then come back.
You can get around this by wrapping the TrackMouseEvent Win32API call.
In the following code, the ResetMouseHover method wraps the API call to get the desired effect: reset the underlying timer that controls when the hover event is fired.
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);
}
}
With the wrapper in place, you can simply call ResetMouseHover(listBox.Handle) at the end of your MouseHover handler and the hover event will fire again even when the cursor stays within the control's bounds.
I'm sure this approach, sticking all the code in the MouseHover handler must result in more MouseHover events firing than are really necessary, but it'll get the job done. Any improvements are more than welcome.
Using the MouseMove event, you can keep track of the index of the item that the mouse is over and store this in a variable that keeps its value between MouseMoves. Every time MouseMove is triggered, it checks to see if the index has changed. If so, it disables the tooltip, changes the tooltip text for this control, then re-activates it.
Below is an example where a single property of a Car class is shown in a ListBox, but then full information is shown when hovering over any one row. To make this example work, all you need is a ListBox called lstCars with a MouseMove event and a ToolTip text component called tt1 on your WinForm.
Definition of the car class:
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); } }
}
Form load event:
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";
}
The tooltip code (including creating the class level variable called 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;
}
}
}
I think the best option, since your databinding your listbox to objects, would be to use
a datatemplate. So you could do something like this:
<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>
Of course you'd replace the ItemsSource binding with whatever your binding source is, and the binding Path parts with whatever public property of the objects in the list you actually want to display.
More details available on msdn
You can use this simple code that uses the onMouseMove event of ListBox in 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);
}
}
Of course you will have to init the ToolTip object in the constructor or some init function:
_toolTip = new ToolTip
{
AutoPopDelay = 5000,
InitialDelay = 1000,
ReshowDelay = 500,
ShowAlways = true
};
Enjoy!
Here is a Style that creates a group of RadioButtons by using a ListBox. All is bound for MVVM-ing. MyClass contains two String properties: MyName and MyToolTip. This will display the list of RadioButtons including proper ToolTip-ing. Of interest to this thread is the Setter for ToolTip near bottom making this an all Xaml solution.
Example usage:
ListBox Style="{StaticResource radioListBox}" ItemsSource="{Binding MyClass}" SelectedValue="{Binding 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>
Using onmouseover you can iterate through each item of the list and can show the 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);
}
}
}
}
Using title attribute, we can set tool tip for each list items in a list box.
Loop this for all the items in a list box.
ListItem li = new ListItem("text","key");
li.Attributes.Add("title","tool tip text");
Hope this helps.