wpf combobox item template for the selected value and search functionality - wpf

I'm using a combobox in my application and I am populating it with classes something like this:
namespace Foo.Bar{
public class Item
{
public string lastName;
public string firstName;
public Foo theMeatyPart;
}
}
I can populate the dropdown with "lastName, firstName" using an itemTamplate but then the selected value shows up as "Foo.Bar.Item". How can I apply the same template to the selectedItem and also, have the search functionality work without overrriding the ToString method of Item?
Here is the xaml:
<Style x:Key="SearchComboStyle" TargetType="ComboBox">
<Style.Setters>
<Setter Property="Width" Value="150"></Setter>
</Style.Setters>
</Style>
<DataTemplate x:Key="SearchComboItemTemplate" >
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="lastName"/>
<Binding Path="firstName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
<ComboBox ItemTemplate="{StaticResource SearchComboItemTemplate}" Style="{StaticResource SearchComboStyle}"
ItemsSource="{Binding Path=PhysiciansList, RelativeSource={RelativeSource AncestorType=local:ExamViewerControl, AncestorLevel=1}}" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" IsEditable="True" TextSearch.TextPath="Person.LastName" />

UPD: Looks like you need to set SelectionBoxItemTemplate.
You can use DisplayMemberPath or TextSearch.TextPath to enable search without modifying ToString().

Related

How to use multibinding on a rectangle-command in mvvm?

I got the following rectangle
<Rectangle
Width="{Binding Width}"
Height="{Binding Length}"
Tag="{Binding Id}"
Name="rectangleDrawnMachine">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="MouseDown">
<cmd:EventToCommand
Command="{Binding Main.UpdateSelectedMachine, Mode=OneWay, Source={StaticResource Locator}}"
PassEventArgsToCommand="True"
CommandParameter="{Binding ElementName=rectangleDrawnMachine}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
The rectangle is bound to a model which is declared in an above ItemsControl. The document-structure is like the following:
<Grid>
<ItemsControl ItemsSource="{Binding AllMachines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Name="canvasDrawnMachines" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Name="rectangleDrawnMachine"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Now my UpdateSelectedMachine-command needs at least three properties of the rectangle:
Position x
Position y
ID / Tag
With the CommandParameter of the rectangle itself my command will get a lot of informations about the rectangle (like the neccessary tag). But it doesnt get the neccessary information about the (X- & Y-)position of the canvas.
So my question is: how to use multibinding on my rectangle-command? And how to transfer the positions of the canvas?
You cannot pass multiple values by using command parameter.
In order to do so, you have to use multi binding.
<cmd:EventToCommand
Command="{Binding Main.UpdateSelectedMachine, Mode=OneWay, Source={StaticResource Locator}}"
PassEventArgsToCommand="True">
<cmd:EventToCommand.CommandParameter>
<MultiBinding Converter="{StaticResource YourConverter}">
<Binding Path="Canvas.Left" ElementName="canvasDrawnMachines"/>
<Binding Path="Canvas.Top" ElementName="canvasDrawnMachines"/>
<Binding Path="Tag" ElementName="canvasDrawnMachines"/>
</MultiBinding>
</cmd:EventToCommand.CommandParameter>
Your converter:
public class YourConverter : IMultiValueConverter
{
public object Convert(object[] values, ...)
{
return values.Clone();
}
}
Then, execution command logic:
public void OnExecute(object parameter)
{
var values = (object[])parameter;
var left = (double)values[0];
var top = (double)values[1];
var tag = values[2];
}
You can get the values of the Canvas.Left and Canvas.Top attached properties of the Rectangle that you are passing as the command parameter to the command like this:
double x = Canvas.GetLeft(rectangle);
double y = Canvas.GetTop(rectangle);
Do you know how to get the position in XAML-way?
Use a MultiBinding with a converter and bind to the Canvas.Left and Canvas.Top properties:
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="(Canvas.Left)" ElementName="canvasDrawnMachines"/>
<Binding Path="(Canvas.Top)" ElementName="canvasDrawnMachines"/>
<Binding Path="Tag" ElementName="canvasDrawnMachines"/>
</MultiBinding>

Multibinding Command Parameters

So I have a context menu for each individual listview item and the listview is bound to a user list. The context menu has a sub-menu that is bound to an observable collection of user statuses. I want to be able to pass the user id from the list view AND the new status ID from the context menu to my update command parameter. I just looked in to MultiBindings and believe this may be a good, long term solution that I can use elsewhere. Here is some code:
ListView from User View:
<ListView Background="Transparent" ItemsSource="{Binding UserList}" SelectionMode="Single">
<ListView.Resources>
<ContextMenu x:Key="Menu" DataContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Name="UserID" Header="{Binding UserID}"/>
<Separator></Separator>
<MenuItem Header="Status" ItemsSource="{Binding DataContext.UserStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
DisplayMemberPath="Name" Name="StatusID">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.UpdateDriverStatus, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</MenuItem.ItemContainerStyle>
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource MultiBindConverter}">
<Binding ElementName="DriverID"></Binding>
<Binding ElementName="StatusID"></Binding>
</MultiBinding>
</MenuItem.CommandParameter> </MenuItem>
</ContextMenu>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu" Value="{StaticResource Menu}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<ItemContainerTemplate>
<TextBlock Text="{Binding UserName}" >
</TextBlock>
</ItemContainerTemplate>
</ListView.ItemTemplate>
</ListView>
User VM:
public class UsersPanelVM : ViewModelBase, INotifyPropertyChanged
{
public ObservableCollection<UserPanelItem> UserList { get; set; }
public ObservableCollection<UserStatusList> UserStatus { get; set; }
private readonly IUserService _userService;
public IUserService UserService { get { return this._userService; } }
public UsersPanelVM(IUserService userService)
{
this._userService = userService;
var model = this.UserService.GetUsers();
this.UserList = model.Users;
var statusmodel = this.UserService.GetUserStatus();
this.UserStatus = statusmodel.UserStatus;
this.UpdateUserStatus = new RelayCommand<UserStatusList>((s) => UpdateStatus(1,s));
}
//The 1, above, is hard coded to test the method call, but ideally that should be the selected UserID
private void UpdateStatus(int ID, UserStatusList s)
{
}
public RelayCommand<UserStatusList> UpdateUserStatus { get; private set; }
}
I'm pretty sure I am 100% lost at this point.
This is not necessary as i already mentioned in your other question because you have all the information except the new status in your object model.
Move the command and the UpdateStatus method into the UserPanelItem's class, which also should hold your ID, then you just need to change the command to:
new RelayCommand(param => UpdateStatus(ID, (UserStatusList)param))
If you really want to do it this way: You again set the CommandParameter of the parent MenuItem whose Command will never even be used, move it to the CommandParameter-Setter's Value in the container style, i.e.
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding ...>
....

Databinding one property on a Dialog from two contols

I have a Window that I'm showing using ShowDialog. One of the values I'm trying to get from the user is size in GB or TB. So I have two controls for this, an IntegerUpDown from the WPF Extended Toolkit and a ComboBox:
<xctk:IntegerUpDown Name="SizeN" Minimum="1" Maximum="1023" Increment="1" Value="100"/>
<ComboBox Name="SizeS" SelectedIndex="0">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
I am setting the Dialog's DataContext to itself. I have defined the Capacity property:
public ulong Capacity { get; set; }
public CustomWindow()
{
InitializeComponent();
DataContext = this;
}
I have already created an IMultiValueConverter, PowerConverter, that takes an int and a string and returns ulong. I think the correct MultiBinding is:
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<MultiBinding Converter="{StaticResource CapacityConverter}">
<Binding ElementName="SizeN" Path="Value" />
<Binding ElementName="SizeS" Path="SelectedValue" />
</MultiBinding>
I can't figure out how to assign this binding to the Capacity property on the Dialog. I want WPF to automagically set the Capacity property for me. Any ideas?
I had to convert Capacity into a DependencyProperty on CustomWindow, set the SelectedValuePath attribute on the ComboBox, and assign the binding to Capacity in the style.
XAML:
<Window xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=WPFToolkit.Extended"
xmlns:local="clr-namespace:MyProject"
x:Class="MyProject.CustomWindow" Title="CustomWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<Window.Style>
<Style TargetType="{x:Type local:CustomWindow}">
<Setter Property="Capacity">
<Setter.Value>
<MultiBinding Converter="{StaticResource CapacityConverter}"
Mode="TwoWay">
<Binding ElementName="SizeNumber" Path="Value"
Mode="TwoWay" />
<Binding ElementName="SizeSuffix" Path="SelectedValue"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
<StackPanel>
<xctk:IntegerUpDown Name="SizeNumber" Minimum="1" Maximum="1023" Increment="1"
Value="100"/>
<ComboBox Name="SizeSuffix" SelectedIndex="0" SelectedValuePath="Content">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
Code behind:
public partial class CustomWindow : Window
{
public CustomWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty CapacityProperty =
DependencyProperty.Register("Capacity", typeof(ulong), typeof(CustomWindow));
public ulong Capacity
{
get
{
return (ulong)GetValue(CapacityProperty);
}
set
{
SetValue(CapacityProperty, value);
}
}
}

Can I do Text search with multibinding

I have below combo box in mvvm-wpf application. I need to implement "Text search" in this..(along with multibinding). Can anybody help me please.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bid Service Cat ID"
Margin="2"></TextBlock>
<ComboBox Width="200"
Height="20"
SelectedValuePath="BidServiceCategoryId"
SelectedValue="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SelectedBidServiceCategoryId.Value}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.BenefitCategoryList}"
Margin="12,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Unfortunately, TextSearch.Text doesn't work in a DataTemplate. Otherwise you could have done something like this
<ComboBox ...>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="TextSearch.Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId"/>
<Binding Path="BidServiceCategoryName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
However this won't work, so I see two solutions to your problem.
First way
You set IsTextSearchEnabled to True for the ComboBox, override ToString in your source class and change the MultiBinding in the TextBlock to a Binding
Xaml
<ComboBox ...
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public override string ToString()
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
//...
}
Second Way
If you don't want to override ToString I think you'll have to introduce a new Property in your source class where you combine BidServiceCategoryId and BidServiceCategoryName for the TextSearch.TextPath. In this example I call it BidServiceCategory. For this to work, you'll have to call OnPropertyChanged("BidServiceCategory"); when BidServiceCategoryId or BidServiceCategoryName changes as well. If they are normal CLR properties, you can do this in set, and if they are dependency properties you'll have to use the property changed callback
Xaml
<ComboBox ...
TextSearch.TextPath="BidServiceCategory"
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public string BidServiceCategory
{
get
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
}
private string m_bidServiceCategoryId;
public string BidServiceCategoryId
{
get
{
return m_bidServiceCategoryId;
}
set
{
m_bidServiceCategoryId = value;
OnPropertyChanged("BidServiceCategoryId");
OnPropertyChanged("BidServiceCategory");
}
}
private string m_bidServiceCategoryName;
public string BidServiceCategoryName
{
get
{
return m_bidServiceCategoryName;
}
set
{
m_bidServiceCategoryName = value;
OnPropertyChanged("BidServiceCategoryName");
OnPropertyChanged("BidServiceCategory");
}
}
}
I don't know if your text search has to search ALL the text, but if you want to search from the category ID, you can just set the TextSearch.TextPath property to BidServiceCategoryId. That should also be helpful for anyone who wants to use multibinding and finds that the text search no longer works... It does work if you explicitly set the TextPath property.

Hide ListViewItem in WPF ListView

How can I hide a ListViewItem in a bound ListView? Note: I do not want to remove it.
Yeah, this is easy.
The first thing you need to do is to add a property to the class you are binding to. For example, if you are binding to a User class with FirstName and LastName, just add a Boolean IsSupposedToShow property (you can use any property you like, of course). Like this:
class User: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
public string LastName { get; set; }
private bool m_IsSupposedToShow;
public bool IsSupposedToShow
{
get { return m_IsSupposedToShow; }
set
{
if (m_IsSupposedToShow == value)
return;
m_IsSupposedToShow = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSupposedToShow"));
}
}
}
Then, remember, to hide some item, don't do it in the UI - no no no! Do it in the data. I mean, look for the User record that you want to hide and change that property in it behind the scenes (like in a View Model) - let the UI react. Make the XAML obey the data.
Like this:
<DataTemplate DataType="{x:Type YourType}">
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSupposedToShow}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<!-- your UI here -->
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="LastName" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
When you change IsSupposedToShow to false, then the XAML understands it is supposed to change the visibility of the whole DataTemplate. It's all wired up for you by WPF and presto, it's what you wanted in your question!
Best of luck!
The approaches that I'd follow, from most to least preferable:
In ListView.ItemContainerStyle, use a DataTrigger to set Visibility based on a bound property.
Use a style in the ItemTemplate, or in the DataTemplate for the items if you're getting default templates from the resource dictionary.
Set the ItemsSource for the ListView to a CollectionView, and handle the CollectionView's Filter event in code-behind. See MSDN's discussion of collection views for details.
Maintain a separate ObservableCollection as the ItemsSource for the ListView and add/remove items as appropriate.
Under no circumstances would I use a ValueConverter, because I have a possibly-irrational distaste for them.
I think that using a CollectionView is probably the most correct way of doing this, but they're kind of inelegant because you have to write an event handler to implement filtering.
Use a style with a trigger to set the items visibility to collapsed.
This page gave me the answer I needed: http://www.abhisheksur.com/2010/08/woring-with-icollectionviewsource-in.html (See section "Filtering".)
Wow, so much easier than XAML.
Example:
bool myFilter(object obj)
{
// Param 'obj' comes from your ObservableCollection<T>.
MyClass c = obj as MyClass;
return c.MyFilterTest();
}
// apply it
myListView.Items.Filter = myFilter;
// clear it
myListView.Items.Filter = null;
The approach with ListView.ItemContainerStyle
<ListView ItemsSource="{Binding Path=Messages}" Grid.Column="1" Grid.Row="1" x:Name="Messages"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False" >
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" >
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} => {1}">
<Binding Path="AuthorName" />
<Binding Path="ReceiverName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Jerry Nixon's answer did not work for me completely. I've got to change the xaml a little bit.
Collapsed list view item was using small layout space when I was using DataTemplate.Resources,
<ItemsControl>
<ItemTemplate>
<DataTemplate>
<Image Visibility='{Binding Converter=my:MaybeHideThisElementConverter}' />
</Image>
</DataTemplate>
</ItemTemplate>
</ItemsControl>
What we're doing here is delegating the decision to your implementation of MaybeHideThisElementConverter. This is where you might return Collapsed if the User property of your object is null, or if the Count is an even number, or whatever custom logic your application requires. The converter will be passed each item in your collection, one by one, and you can return either Visibility.Collapsed or Visibility.Visible on a case by case basis.

Resources