I am trying to create a WPF ComboBox control which contains a clear button when something is selected. The control should have two states: if something is selected the control looks like a label with a clear button. If nothing is select then a normal ComboBox is display. The two states are showing in the picture below.
Researching my problem I came across the following SO questions which are very similar to mine:
Adding a button to a combobox in wpf
How to subclass a WPF ComboBox to add an extra button
Both suggest subclassing the ComboBox providing a modified template with the extra button. But this is where I am a tad confused. The answer by John Bowen to the second linked question indicates that I should copy the ComboBox's default template; modifying it to include a button getting the template from Blend. Not being proficient with blend I found the template on MSDN here:
http://msdn.microsoft.com/en-us/library/ms752094(v=vs.85).aspx
My problem is I am not quite sure what I should change. Looking at the Default template I think I need to do something along the lines of:
Create a new 'IsSelected' property which I can hook triggers to.
Add a control template for a Clear Button with trigger attached to IsSelected which hides the button.
Attached a IsSelected Trigger to the ComboBoxToggleButton control template to hide it upon selection.
Somehow re-size the PART_EditableTextBox textbox in the ComboBox Template when IsSelected is true.
Does this seem right and any pointers on how I do it or other suggestions if I am barking up the wrong tree.
Maybe you will accept a simpler solution - just place a TextBlock with a Button above your ComboBox?
The xaml will look like this:
<Grid>
<ComboBox ItemsSource="{Binding ...}" x:Name="cbox"/>
<Grid Background="Gray" Visibility="{Binding SelectedItem, ElementName=cbox, Converter={StaticResource NullItem2Visibility}}">
<TextBlock Text="{Binding SelectedItem, ElementName=cbox}" HorizontalAlignment="Left"/>
<Button Content="Clear" HorizontalAlignment="Right" Click="ClearItem"/>
<Grid>
</Grid>
Codebehind will have method ClearItem:
public void ClearItem(object sender, EventArgs e){
cbox.SelectedItem=null;
}
And the converter to show and hide textblock and a button:
class NullItem2Visibility:IValueConverter{
public object Convert(object value, Type type, object parameter, CultureInfo i){
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(...){...}
}
Related
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.
I have a combo box in a wpf c# application. In the xaml i am trying to do the following.
The ItemsSource comes from one variable.
SelectedItem sets a value on another variable
But i want the text displayed to come from a new variable.
How do i stop making the selected itemssource appear as the main text?
<ComboBox x:Name="ComboPlay" FontSize="14" MinHeight="20" Margin="0,2,4,4" Grid.Row="1" Grid.Column="3" MinWidth="160"
ItemsSource="{Binding ComboBoxList}"
SelectedItem="{Binding OutputChannel.Value, Converter={StaticResource ResourceKey=ValueToStringConverter}}" Grid.ColumnSpan="1"
IsEnabled="{Binding IsDriveChoiceEnabled}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
If you mean changing the display of the item currently selected (the portion of the control shown when the dropdown is closed), take a look at Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
Honestly, a quick search dug up that one and many similar ones. Probably the simplest way, like the linked answer, is to figure out if your item is wrapped in a ComboBoxItem and display it differently then. Or you could re-template the ComboBox. Or you could derive from it (and re-template it) and provide a separate dependency property for the template of the selected item, if you expect to reuse it in different contexts. The sky's the limit.
I have this WinRT XAML:
<ComboBox x:Name="comboxGroupName" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Margin="4" Width="200" Height="36" HorizontalAlignment="Left" ></ComboBox>
When I click in it to enter a new value, though, it seems to convert itself into a readonly textbox (it loses its down arrow and disallows any typing into it). What do I need to do to allow adding values into the comboBox? Or do I need to use a separate TextBox to do that (I reckon so, but I'd like to avoid that if reasonably possible)?
It looks like your only option will be to use a seperate TextBox. There is an IsEditable property, but it states:
Gets a value that indicates whether the user can edit text in the text box portion of the ComboBox. This property always returns false.
and the ComboBox Page states:
You populate the ComboBox by adding objects directly to the Items collection or by binding the ItemsSource property to a data source. Items added to the ComboBox are wrapped in ComboBoxItem containers.
I am writing a windows-phone 7 application. I've got a page with a list of TextBlock(s) contained in a ListBox. The behavior I want is that upon clicking one of those TextBlock(s) the page is redirected to a different one, passing the Text of that TextBlock as an argument.
This is the xaml code: (here I am binding to a collection of strings, and the event MouseLeftButtonDown is attached to each TextBlock).
<ListBox x:Name="List1" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock MouseLeftButtonDown="List1_MouseLeftButtonDown" Text="{Binding}"
FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But this has been unsuccessful for me. I have tried attaching MouseLeftButtonDown event to either the individual TextBox(es) or to the ListBox. And I have had exceptions raised as soon as I use NavigationService.Navigate(uri). Which event should be attached? Should the event be attached to the individual items or to the list as a whole?
I have found a way to work around this problem by populating ListBox with HyperlinkButton(s). However, I would like to understand why the TextBox approach did not work.
This is my first attempt with Silverlight, so I might be missing something basic here.
There are a few ways to do this but I'll walk you through one of the the simplest (but not the purest from an architectural perspective).
Basically you want to find out when the selection of the ListBox changes. The ListBox raises a SelectionChanged event which can be listened to in the code behind.
<ListBox x:Name="List1" ItemsSource="{Binding}" SelectionChanged="SelectionChangedHandler" SelectionMode="Single" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="20"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then have a handler something like:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
}
One thing you'll need to be aware of is that ListBoxes support multiple selection. For this reason, the event arguments give you back a list of the selected items. For simplicity, all I've done is taken the first value from this list and used that as the navigation value. Notice how I've also set the SlectionMode property of the ListBox to Single which will ensure the user can only select one item.
If I were doing this for real I'd look into creating an TriggerAction tat can be hooked up to an event trigger through xaml which will remove the for code behinds. Take a look at this link if you're interesetd.
In addition to Chris' and James' replies, I'd add that you will also need to clear the listbox selection in the event handler, otherwise the user won't be able to tap the same item twice on the listbox (because the item will already be selected).
Using James' approach, I would change the SelectionChangedHandler() implementation as follows:
private void SelectionChangedHandler(object sender, SelectionChangedEventArgs e)
{
// Avoid entering an infinite loop
if (e.AddedItems.Count == 0)
{
return;
}
IList selectedItems = e.AddedItems;
string val = selectedItems.OfType<string>().FirstOrDefault();
NavigationService.Navigate(new Uri(val));
// Clear the listbox selection
((ListBox)sender).SelectedItem = null;
}
What I would recommend is binding the SelectedItem property of the ListBox to a property in your ViewModel. Then, on the ListBox's SelectedItemChanged event, navigate to to the appropriate URL passing the data key on the QueryString, or upgrade to something like MVVM Light and put the actual SelectedItem object on the message bus for the child window to pick up. I have a sample of this second method on my Skydrive that you can check out.
HTH!
Chris
I am trying to implement what I used to take for granted in Winforms applications. I am a Silverlight noob, so hopefully all this is elementary.
I have a listbox in a Silverlight 4 app. I'd like to do the following:
Right-click on the listbox
Have the item under the location where I click highlight itself
I'd like a context menu to popup (with my own items in the context menu)
From my research so far, it appears that there is no ContextMenu construct in Silverlight, instead we have to build up a Grid/Canvas structure and attach it to a Popup object, which is what is then popped up.
My questions are as follows:
To accomplish #2, I need some kind of hit test on the listbox. I can't figure out how to do that and my google-fu isn't helping.
Once I do identify the index under the mouse, how do I actually select the item?
Is there a reusable Context menu component somewhere that I can use? Extra credit if the component allows arbitrary sub-menus.
I've been looking around for the same thing. I checked the Silverlight Control Toolkit at CodePlex and went through the samples (it's a very handy resource) and here's what I found to be the solution to what you asked:
Create an ItemTemplate for your ListBox
in the part that you want to be "right-clickable" of your ItemTemplate set the attached property ContextMenuService.ContextMenu that exists within the System.Windows.Controls.Input.Toolkit namespace
add MenuItem controls to your ContextMenu and set the Click property to the corresponding click event handler
in the event handler, get the DataContext from the sender (you can use that to find the corresponding element in the ListBox)
to make that element Selected, just set the SelectedItem property in the list box to it
Add any custom logic to the event handler
There's an example in the samples page, just go to "Input->ContextMenu" from the navigation pane.
If you want something concise, Here's a simplified example:
<ListBox ItemsSource="{StaticResource People}"
Name="myListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<controlsInputToolkit:ContextMenuService.ContextMenu>
<controlsInputToolkit:ContextMenu>
<controlsInputToolkit:MenuItem Header="Show in MessageBox"
Click="show_Click" />
</controlsInputToolkit:ContextMenu>
</controlsInputToolkit:ContextMenuService.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
with:
xmlns:controlsInputToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
for the code:
private void show_Click(object sender, RoutedEventArgs e)
{
var person = ((MenuItem)sender).DataContext as Person;
if (null == person) return;
MessageBox.Show("My Name is: " + person.Name);
myListBox.SelectedItem = person;
}
I hope this helps :)
There's the MouseRightButtonDown event. If you bind that on the ListBox:
<ListBox Height="143" Name="listBox1" Width="218"
MouseRightButtonDown="listBox1_MouseRightButtonDown" />
you'll get what you need. The code behind is:
private void listBox1_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
}
The MouseButtonEventArgs will give you the position via the GetPosition method.