I'll give some details below, but briefly I have a problem wherein a button in my WPF application will disable itself if I click somewhere else in the application. This button is linked to a Command but there is absolutely NOTHING bound to the IsEnabled property of the button and the Command CanExecute callback just sets e.CanExecute to true.
I saw this similar SO question (Weird problem where Button does not get re-enabled unless the mouse is clicked) but it was not helpful.
The application is simple and consists of a DockPanel divided into 4 parts. The left section contains a DataGrid and the right section contains a UserControl that also contains a couple DataGrids as well as the offending Button. It's so weird, but if I click on any row in the left DataGrid, the Button in the right section becomes disabled. The only way I can then re-enable it is to click on any row in either of the DataGrids in the right section!
Again, there IS absolutely nothing bound to the IsEnabled property of the button or any other code/markup implicitly in place that would allow this to happen.
Some relevant code snippets:
UserControl Command bindings:
<UserControl.CommandBindings>
<CommandBinding Command="cmd:DBScriptCommands.LoadScripts" CanExecute="cmdLoadScripts_CanExecute" Executed="cmdLoadScripts_Executed" />
<CommandBinding Command="cmd:DBScriptCommands.RunScripts" CanExecute="cmdRunScripts_CanExecute" Executed="cmdRunScripts_Executed" />
</UserControl.CommandBindings>
Button xaml:
<Button IsEnabled="True" x:Name="btnLoadScripts" Command="cmd:DBScriptCommands.LoadScripts">
<StackPanel HorizontalAlignment="Stretch">
<Image Source="/Images/folder-open.png" />
<TextBlock>Load Scripts</TextBlock>
</StackPanel>
</Button>
CanExecute for Command:
private void cmdLoadScripts_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
This has me baffled and any ideas are appreciated. If more information is needed, please let me know.
Edit 1:
Seems like it is something to do with the Command (still not sure what) - if I remove the Command parameter from the Button, the problem goes away - not useful because I want the button to trigger the Command, but interesting. In the CanExecute(), if I set e.CanExecute to false, the button is always disabled which makes sense. When it is set to true (like it is now) then I have the problem I've describe where it seems like something is setting it to false by magic sometimes.
Thanks
Related
I'm sorry for this being so wordy, but I want to make the situation perfectly clear. Please, if you are a WPF pro, take a look.
There are two CollectionViewSource bound to ItemsControls that use UserControl Views to display either StackPanels or custom Buttons. There is one for each side shown in the screenshot below. The problem I'm encountering is that when the parent collection property is set, all of the buttons in the DataTemplate view are disabled. Even the 2 buttons higher up are having the same problem even though they worked before my recent edits.
If I click on the form, or press any key, the buttons enable. As soon as the property is reset to a newly edited and sorted collection, they disable again. Rinse and repeat. This is what it looks like. The first frame is how it starts (gray using StackPanel), the 2nd is what it looks like when the RFS button is clicked, and the 3rd frame is what happens when I click anywhere or press a key.
I've been going in circles trying out things. The only thing that seems to work is a code-behind workaround that sets focus to one thing and then back. However, that would not be good for the user if they are trying to use one of the other dashboard items.
Since the WPF for all of this is very massive, I'll try to include just the relevant parts. These are the ItemsControls on the TabItemControl (UserControl).
<!-- BID (SELL) DEPTH -->
<ItemsControl Grid.Row="0" Grid.Column="0" x:Name="bidDepthList" ItemsSource="{Binding Source={StaticResource BidDepthCollection}}"
Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
<v:DepthLevelRowView x:Name="BidDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- ASK (BUY) DEPTH -->
<ItemsControl Grid.Row="0" Grid.Column="1" x:Name="askDepthList" ItemsSource="{Binding Source={StaticResource AskDepthCollection}}"
Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
<v:DepthLevelRowView x:Name="AskDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The view being used has 4 "controls" inside of a grid. Only one of them is displayed at a time depending on the state (RFS OFF/RFS ON) and which side they are on (Sell/Buy). The others are collapsed. As you can see, this is fine.
The only common factor between them is that they have their Command set, as do most of the controls at the top that are disabling/enabling correctly. The fact that the buttons enable correctly if any mouse or keyboard action is taken tells me that the CanExecute handler is working, just not immediately. The other controls started working after I made these changes, but then the big buttons started misbehaving like the depth buttons have been doing.
I've tried using CommandManager.InvalidateRequerySuggested(); after altering the collections, but that didn't help.
NOTE: This is also happening even for something as simple as this:
<Button x:Name="TestBuyButton" Command="{x:Static ptcommands:OrderCommands.Buy}" CommandTarget="{Binding RelativeSource={RelativeSource Self}}" Content="Test Buy" />
I know the booleans are set correctly because I added test CheckBoxes to display the current value with IsChecked. And, they all enable, including the extremely basic Button, as soon as any input action is taken.
Is there something else I'm missing or a different approach I can take?
EDIT: I ended up using the Dispatcher to invoke my display update routine from the event thread over to the UI thread. The boolean values get set, but WPF still didn't requery the Command. BUT.. the CommandManager.InvalidateRequerySuggested() call then worked! The only thing I don't like about that is it sounds like a broadcast invalidation. I don't want all commands to be requeried. I just want the Buy and Sell commands to requery. I've tried all sorts of weirdness trying to get just those to work, but nothing has worked so far other than the global.
EDIT 2: It appears as though the InvalidateRequerySuggested is the way to go.
From MSDN CommandManager.InvalidateRequerySuggested:
The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.
This would explain why the focus changes and keypresses would cause the buttons to enable. The page also shows putting the call in a timer. Because of this, I assume it is not as resource intensive as I thought. So, I guess it's my permanent solution.
I had to make a few changes to get the buttons to enable without focus changes.
The boolean values were being set within a method called from an event thread. I had tried calling CommandManager.InvalidateRequerySuggested along with it, but nothing happened. Eventually I thought it might have something to do with the thread. This is what ended up resolving it.
In the parent form that the tab manager class (containing the event handlers and other logic) has access to, I added an invoke method:
Public Sub InvokeOnUiThread(ByRef uiAction As Action, Optional ByRef doAsync As Boolean = False)
Dim dispatchObject As Dispatcher = orderTicketView.Dispatcher
If (dispatchObject Is Nothing OrElse dispatchObject.CheckAccess()) Then
uiAction()
Else
If doAsync Then
dispatchObject.BeginInvoke(uiAction, DispatcherPriority.Normal)
Else
dispatchObject.Invoke(uiAction, DispatcherPriority.Normal)
End If
End If
End Sub
In the tab manager event handler, I changed it to call the update routine through the invoker:
formOrderTicketView.InvokeOnUiThread(New Action(AddressOf UpdateButtons))
At the bottom of the UpdateButtons method, I added a call to the CommandManager if a change has been made that would require a requery:
CommandManager.InvalidateRequerySuggested()
I do not know what type of performance hit this would have, but apparently WPF executes it in its own way when focus changes and such. Calling it directly is the advised way to force it.
From MSDN CommandManager.InvalidateRequerySuggested:
The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.
Since it is working now, I am taking this as the resolution.
The command that's bound to the button...is there a CanExecute delegate with it? If so, you have to raise CanExecuteChanged when that delegate should be re-evaluated. Also, make sure the implementation of CanExecute isn't broken and incorrectly returning false;
I'm trying to find the best solution for a TabControl that both support a close button on each TabItem, and always show a "new tab button" as the last tab.
I've found some half working solutions, but i think that was for MVVM, that I'm not using. Enough to try to understand WPF =)
This is the best solution I've found so far:
http://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF
A solution that i actually understand. But the problem is that it is using the ItemsSource, and i don't want that. I want to bind the ItemsSource to my own collection without having to have special things in that collection to handle the new tab button.
I've been search for days now but cant find a good solution.
And I'm really new to WPF, otherwise i could probably have adapted the half done solutions I've found, or make them complete. But unfortunately that is way out of my league for now.
Any help appreciated.
I have an open source library which supports MVVM and allows extra content, such as a button to be added into the tab strip. It is sports Chrome style tabs which can tear off.
http://dragablz.net
This is bit of a dirty way to achieve the Add (+) button placed next to the last TabItem without much work. You already know how to place a Delete button next to the TabItem caption so I've not included that logic here.
Basically the logic in this solution is
To bind ItemsSource property to your own collection as well as
the Add TabItem using a CompositeCollection.
Disable selection of
the Add(+) TabItem and instead perform an action to load a new tab when it
is clicked/selected.
XAML bit
<TextBlock x:Name="HiddenItemWithDataContext" Visibility="Collapsed" />
<TabControl x:Name="Tab1" SelectionChanged="Tab1_SelectionChanged" >
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.MyList, Source={x:Reference HiddenItemWithDataContext}}" />
<TabItem Height="0" Width="0" />
<TabItem Header="+" x:Name="AddTabButton"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
The code behind
private void Tab1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(AddTabButton))
{
//Logic for adding a new item to the bound collection goes here.
string newItem = "Item " + (MyList.Count + 1);
MyList.Add(newItem);
e.Handled = true;
Dispatcher.BeginInvoke(new Action(() => Tab1.SelectedItem = newItem));
}
}
You could make a converter which appends the Add tab. This way the collection of tabs in you viewmodel will only contain the real tabs.
The problem is then how to know when the Add tab is selected. You could make a TabItem behavior which executes a command when the tab is selected. Incidentally I recommended this for another question just recently, so you can take the code from there: TabItem selected behavior
While I don't actually have the coded solution, I can give some insight on what is most likely the appropriate way to handle this in a WPF/MVVM pattern.
Firstly, if we break down the request it is as follows:
You have a sequence of elements that you want to display.
You want the user to be able to remove an individual element from the sequence.
You want the user to be able to add a new element to the sequence.
Additionally, since you are attempting to use a TabControl, you are also looking to get the behavior that a Selector control provides (element selection), as well as an area to display the element (content) which is selected.
So, if we stick to these behaviors you'll be fine, since the user interface controls can be customized in terms of look and feel.
Of course, the best control for this is the TabControl, which are you already trying to use. If we use this control, it satisfies the first item.
<TabControl ItemsSource="{Binding Path=Customers}" />
Afterwards, you can customize each element, in your case you want to add a Button to each element which will execute a command to remove that element from the sequence. This will satisfy the second item.
<TabControl ...>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CustomerId}" />
<Button Command="{Binding Path=RemoveItemCommand, Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
<TabControl.ItemTemplate>
</TabControl>
The last part is a bit more difficult, and will require you to actually have to create a custom control that inherits from the TabControl class, add an ICommand DependencyProperty, and customize the control template so that it not only displays the TabPanel, but right next to it also displays a Button which handles the DependencyProperty you just created (the look and feel of the button will have to be customized as well). Doing all of this will allow you to display your own version of a TabControl which has a faux TabItem, which of course is your "Add" button. This is far far far easier said than done, and I wish you luck. Just remember that the TabPanel wraps onto multiple rows and can go both horizontally or vertically. Basically, this last part is not easy at all.
FINAL NOTE
Final solution found in another post
Although I appreciated the clarification that was provided, the ultimate solution was in-fact provided by another solution as linked above. No matter WHAT I tried, the binding via the "Element Name" component was not working. I had to go based on the "Relative" hierarchy up the datagrid...
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}"
Height="16" Width="16" HorizontalAlignment="Center" >
<Image Source="MyButtonImage.png" IsHitTestVisible="True"/>
</Button>
Hope something not too complicated in WPF / MVVM environment. Here's the scenario.
I have a Window (.xaml) and a corresponding View Model (.cs). The form displays fine with all the data bindings no problem. (note: this is NOT done via any commercial "framework" )
One of the controls that is in the view window is a custom user control of a datagrid with all pre-defined columns, headings and content to be displayed when the view is shown. This works all no problem even though the control is not directly "defined" in the main window .xaml file, but just dropped on the form as the user control itself (which has its own obvious .cs code-behind).
With the main window's "DataContext" pointing to the View Model, and the user control that has a datagrid
<DataGrid AutoGenerateColumns="False"
Name="dataMyStuff"
ItemsSource="{Binding Path=MyTablePropertyOnViewModel,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}" ... />
Now, what I'm looking for. On this data grid, I have a column that has an image in the first column. When I click on this image, I want to print a report specific to the record as represented by this row (it has a PK value I use). So, how do I tell the image "KeyUp" event to go to the View Model event handler since that is where the data is, and some other methods I'll need for preparing the call to the report. The view portion of the grid is for cosmetic display to the user, and thus no "functionality" directly in this control.
-- EDIT -- per progress from answers
I've adjusted my datagrid per comments from Josh and Rachel, however, something still does not appear to be quite right... Seeing the button was using a "Command" instance, I interpreted this as it needed to attach to an instance of an "ICommand" interface object on my view model. So, I created an instance. I know the command handler works as it is also used for common things like Add, Edit, Save, Cancel, Exit, etc... So I have a new one for this printing purpose. For simplicity, I have it created as an Always Execute, so there is no method to handle the "CanExecute" portion of the control. I've set the button's "Command" to almost all iterations I could think of an still nothing, but here's an update of what I'm seeing going on.
<UserControl>
<Data grid columns / template, etc to the button>
<DataTemplate>
<Button Name="btnPrintReport"
Command="{Binding DataContext.MyPrintCommand}" >
<Image Source="myPrintImage.png"/>
</Button>
</DataTemplate>
</Data grid columns, etc>
</UserControl>
In my ViewModel class (myICommandButtonHandler inherits from ICommand)
private myICommandButtonHandler myPrintCommand;
public myICommandButtonHandler MyPrintCommand
{
get { if (myPrintCommand == null)
myPrintCommand = new myICommandButtonHandler(myPrint);
return myPrintCommand;
}
}
private void myPrint()
{
MessageBox.Show( "Doing the print job..." );
}
Now, what I'm seeing. During step through initialization of all the controls and such. I click menu item to call my Window to be displayed. FIRST, it creates an instance of the View Model controller. THEN, it calls the Window and passes in the View Model controller as parameter so it is immediately set at the Window level as the "DataContext" of the window. The main window then goes into it's "InitializeComponents" call and starts to build out all the other controls embedded, including this separate class that contains the data grid in question. At the constructor of this usercontrol (that has the datagrid), there is no "data context" set yet as the rest of the controls have not been initialized yet, and I don't know why / when the "bindings" apparently get "hooked" in. So, it appears that trying to do the binding to the data grid's command button are failing. HOWEVER, at run-time, the actual data IS updated in the grid, so I know that much is working.
So, the data grid has its "ItemsSource" set to a property of a "DataView" property on the view model, but the binding of the "button" doesn't appear to see the "MyPrintCommand" handler that I thought would get the hit.. and its action is to just display a message box (for now).
Usually I use an AttachedCommand Behavior which allows me to bind Events to ViewModel Commands. For example, you could use
<Image ...
local:CommandBehavior.Event="KeyUp"
local:CommandBehavior.Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
local:CommandBehavior.CommandParameter="{Binding }"/>
I'd would recommend using a different event than KeyUp, since I don't think Images can have Keyboard focus so the KeyUp event will never get fired.
A better alternative is to use a Button and overwrite it's Template to be your Image. This will maintain the Click functionality, and give you access to Command and CommandParameter properties
<Button Command="{Binding DataContext.PrintCommand, ElementName=dataMyStuff}"
CommandParameter="{Binding }">
<Button.Template>
<Image ... />
</Button.Template>
</Button>
Also, the CommandParameter="{Binding }" will simply pass the current DataRow's DataContext (your data object) to the command
Change the data template to be a button that has a image as its content. Use the command and commandparameter properties on the button to call your printing method. You can declare your print command in your viewmodel, and bind to it. Your parameter could be the selected row in your datagrid.
I've been pounding away at this issue for a little while, and have only found part of the solution.
I'm trying to set up a TabControl so that I can in some cases prevent the user from changing the currently selected tab. When the user is prevented from changing the currently selected tab, then they are shown a dialog box.
I have already read the following documents:
WPF - reset ListBox scroll position when ItemsSource changes
http://wizardsofsmart.net/uncategorized/itemssourcechanged-event-using-attached-dependency-properties/
http://joshsmithonwpf.wordpress.com/2009/09/04/how-to-prevent-a-tabitem-from-being-selected/
http://social.expression.microsoft.com/Forums/en-US/wpf/thread/f7b46018-1e97-4bbe-ada8-49b75dbc1da2/
I have implemented the solution indicated in the 3rd link (though all of the above create the same error seen below). And it works, but...
Things mess up thoroughly if the user does the following:
attempts to change the tab when such an action is disallowed. The MessageBox pops up with the error.
the user clicks "OK" and is returned to the original window.
the user tries again to change the tab. No MessageBox appears.
if the user minimizes the window, and then maximizes it again, then the MessageBox that was supposed to appear earlier appears.
the user clicks "OK" and is returned to the original window... but the tab has been changed to the one they selected before, even though they should not be able to change tabs.
This is obviously not ideal behavior. Why isn't the MessageBox appearing the second time, and why is the tab changing when it should be disallowed from doing so?
If I remove the MessageBox part, it works fine.
Here is the code for the TabControl.SelectionChanged event handler:
bool _isChanging = false;
private void tabControlForNavigation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_isChanging && canChangeTabs.IsChecked.HasValue)
{
_isChanging = true;
bool canLeave = canChangeTabs.IsChecked.Value; //normally this would be replaced by a check in the ViewModel
if (!canLeave)
{
int prevIndex = tabControlForNavigation.Items.IndexOf(tabControlForNavigation.SelectedContent);
tabControlForNavigation.SelectedIndex = prevIndex;
MessageBox.Show("Can't change tabs!"); //if I comment out this line, everything works fine.
}
_isChanging = false;
}
}
I am using MVVM to implement this. The Window looks like this:
<Window x:Class="TestTabControlSwitching.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox x:Name="canChangeTabs"
Content="Can Change Tabs"
IsChecked="True" />
<TabControl x:Name="tabControlForNavigation"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Collection}"
SelectedItem="{Binding SelectedItem}"
SelectionChanged="tabControlForNavigation_SelectionChanged"
Margin="4"
HorizontalAlignment="Stretch">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
I'm omitting the rest of the code for sake of brevity- there is a pretty straight-forward ViewModel structure backing the window.
As you noticed, the problem is the MessageBox inside the event handler. The focus will change to the MessageBox and you can get all kind of undesired effects. I've had my own problems with this.
Here is a couple of SO question on the same subject
WPF: Does MessageBox Break PreviewMouseDown?
Wpf stop routing event when MessageBox appear?
If you must display a message to the user then an alternate approach might be to create a new Window which you style like a MessageBox and then call Show (not ShowDialog) on it inside the event handler.
I know this post is a bit old, but I have a very easy way to accomplish this:
Use the tab_Enter event and create a method that performs your check and displays a MessageBox to the user and then set myTabs.SelectedIndex to the prior index. A simple example:
private void someTab_Enter(object sender, EventArgs e)
{
if (myCondition)
{
MessageBox.Show("Sorry, myCondition will not let you move to this tab.");
myTabs.SelectedIndex = someOtherTabIndex;
}
}
This was a very detailed question. I had the same problem you had (i.e. the message box doesn't display on 2nd or 3rd selection changed until you minimize and maximize the window) and after much debugging and multiple google searches, stumbled on the below linked MSDN forum post.
[TabControl SelectionChanged Strange Behaviour?]
Please ignore the poorly formatted question and answer. But as mentioned in the answer, putting it inside a dispatcher and focussing the selected tab after setting the index resolved the issue for me.
You are missing an easy trick. Just make focusable=False for the Tab header.
<TabItem Header="MY TAB" Focusable="False">
You could bind this property to your view model.
<TabItem Header="MY TAB" Focusable="{Binding Bool_CanHasCheeseBurger}">
I have a ListBox that has a style defined for ListBoxItems. Inside this style, I have some labels and a button. One that button, I want to define a click event that can be handled on my page (or any page that uses that style). How do I create an event handler on my WPF page to handle the event from my ListBoxItems style?
Here is my style (affected code only):
<Style x:Key="UsersTimeOffList" TargetType="{x:Type ListBoxItem}">
...
<Grid>
<Button x:Name="btnRemove" Content="Remove" Margin="0,10,40,0" Click="btnRemove_Click" />
</Grid>
</Style>
Thanks!
Take a look at RoutedCommands.
Define your command in myclass somewhere as follows:
public static readonly RoutedCommand Login = new RoutedCommand();
Now define your button with this command:
<Button Command="{x:Static myclass.Login}" />
You can use CommandParameter for extra information..
Now last but not least, start listening to your command:
In the constructor of the class you wish to do some nice stuff, you place:
CommandBindings.Add(new CommandBinding(myclass.Login, ExecuteLogin));
or in XAML:
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static myclass.Login}" Executed="ExecuteLogin" />
</UserControl.CommandBindings>
And you implement the delegate the CommandBinding needs:
private void ExecuteLogin(object sender, ExecutedRoutedEventArgs e)
{
//Your code goes here... e has your parameter!
}
You can start listening to this command everywhere in your visual tree!
Hope this helps
PS You can also define the CommandBinding with a CanExecute delegate which will even disable your command if the CanExecute says so :)
PPS Here is another example: RoutedCommands in WPF
As Arcturus posted, RoutedCommands are a great way to achieve this. However, if there's only the one button in your DataTemplate then this might be a bit simpler:
You can actually handle any button's Click event from the host ListBox, like this:
<ListBox Button.Click="removeButtonClick" ... />
Any buttons contained within the ListBox will fire that event when they're clicked on. From within the event handler you can use e.OriginalSource to get a reference back to the button that was clicked on.
Obviously this is too simplistic if your ListBoxItems have more than one button, but in many cases it works just fine.
You could create a user control (.ascx) to house the listbox. Then add a public event for the page.
Public Event btnRemove()
Then on the button click event in the usercontrol
RaiseEvent btnRemove()
You can also pass objects through the event just like any other method. This will allow your user control to tell your page what to delete.