I have a collection of a few thousand elements and I want to display only a subset of them. Is there a way to bind the collection to a view such that only certain elements, e.g. those with property "Show == true", are displayed? If so, would it still create thousands of UI elements? Or do I have to create a new list of the to-be-shown elements?
Check CollectionViewSource Class and CollectionViewSource.Filter Event
View(partial example):
<Grid>
<Grid.DataContext>
<wpfCalc:StudentList/>
</Grid.DataContext>
<Grid.Resources>
<CollectionViewSource
Source="{Binding Students,Mode=OneWay}" x:Key="StudentsCollViewSource"
Filter="StudentsCollViewSource_OnFilter"/>
</Grid.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource StudentsCollViewSource}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name,Mode=OneTime}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And code behind filter implementation:
private void StudentsCollViewSource_OnFilter(object sender, FilterEventArgs e)
{
var s = e.Item as Student;
e.Accepted = s != null && !string.IsNullOrWhiteSpace(s.Name);
}
There are two methods. First one is to use a converter.
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="converter"/>
</Window.Resources>
<StackPanel>
<ListView x:Name="listView" ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Name}" Visibility="{Binding IsActive, Converter={StaticResource converter}}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
//Code Behind
public class BoolToVisibilityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return false; // not needed
}
#endregion
}
Second method is to use linq
List<MyData> list
{
get
{
if(list!=null)
return list.where( p => p.IsActive == true );
return null;
}
set
{
if(list!=value)
list = value;
}
}
Related
I am trying to bind a ComboBox ItemsSource to the TextWrapping enum within the System.Windows namespace. The end result would be a drop down where the user can select which type of text wrapping to apply for a given object within my application. Everything works fine when I bind to a custom enum, but I can't figure out what path/source I need to use to bind to an enum within the System.Windows namespace. How can I access this namespace through data binding?
<DataTemplate
DataType="{x:Type MyObjectWrapper}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Text Wrapping" VerticalAlignment="Center" Margin="5,5,0,5"/>
<ComboBox
ItemsSource="{Binding Source={???}, Converter={local:MyEnumConverter}}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path = TextWrapping}"
VerticalAlignment="Center"
Margin="5"
/>
</StackPanel>
</DataTemplate>
Update: My enum converter just needs the enum class passed in the xaml, which looks like this for custom enums:
<ComboBox
ItemsSource="{Binding Path=MyCreatedEnum, Converter={local:MyEnumConverter}}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path = TextWrapping}"
VerticalAlignment="Center"
Margin="5"
/>
Found this over the web under this link.
http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/
Here is the modified code to suite your requirement.
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<local:EnumToListConverter x:Key="enumToListConv" />
</Grid.Resources>
<ComboBox
Margin="5"
VerticalAlignment="Center"
ItemsSource="{Binding Source={local:EnumBindingSource {x:Type sysWin1:TextWrapping}}, Converter={StaticResource enumToListConv}}"
SelectedValuePath="Value" />
</Grid>
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
public Type EnumType
{
get { return this._enumType; }
set
{
if (value != this._enumType)
{
if (null != value)
{
Type enumType = Nullable.GetUnderlyingType(value) ?? value;
if (!enumType.IsEnum)
throw new ArgumentException("Type must be for an Enum.");
}
this._enumType = value;
}
}
}
public EnumBindingSourceExtension() { }
public EnumBindingSourceExtension(Type enumType)
{
this.EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (null == this._enumType)
throw new InvalidOperationException("The EnumType must be specified.");
Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
return actualEnumType;
}
}
public class EnumToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var items = Enum.GetValues((Type)value);
return items;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a listview containing a text block and another list.
I want when the user clicks on a nested listviewitem the parent listviewitem to be also selected.
Example:
In the above image Lista2 (parent list item) is selected.
The user clicks on Linia6 from Lista1 to select it. When Linia6 from Lista1 (nested list item) is selected, also Lista1 (parent list item) to be selected.
Also if it is possible to do this on code behind, not XAML
My code
<ListView ItemsSource="{Binding listsToDisplay}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled" Width="200">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Width="250" Text="{Binding listName}"></TextBlock>
<ListView ItemsSource="{Binding listContent}">
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
If the SelectedItem property of the nested ListView is not used by other means, you can bind it to ListViewItem.IsSelected of it's parent, using a converter. Note, the SelectionMode="Single" on the parent listview is important to avoid double-selection by the converter.
<Grid x:Name="grid1">
<Grid.Resources>
<local:SelectedItemTransferConverter x:Key="ItemSelectedConverter"/>
</Grid.Resources>
<ListView ItemsSource="{Binding listsToDisplay}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding listName}"/>
<ListView
ItemsSource="{Binding listContent}"
SelectedItem="{Binding IsSelected,RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}},Converter={StaticResource ItemSelectedConverter}}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
The converter code:
public class SelectedItemTransferConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool || value is bool?)
{
if ((bool?)value == false)
{
return null;
}
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(bool) || targetType == typeof(bool?))
{
return value != null;
}
return Binding.DoNothing;
}
}
I want to bind the selection change of the combo box to update my list box, but my xaml code is probably wrong.
This is my Collection that takes the data from the Service.
public class WorkersCollection
{
private WorkerClient client = new WorkerClient();
public ObservableCollection<Worker> Workers { get; set; }
public WorkersCollection()
{
Workers = new ObservableCollection<Worker>();
}
public ICollection<Worker> GetAllWorkers()
{
foreach (var worker in client.GetAllWorkers())
{
Workers.Add(worker);
}
return Workers;
}
}
My DataContext is workers:
public partial class MainWindow : Window
{
WorkersCollection workers;
public MainWindow()
{
InitializeComponent();
workers = new WorkersCollection();
this.DataContext = workers;
workers.GetAllWorkers();
}
}
and in XAML:
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}" ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem}" />
How can I fix it?
ItemsSource property of class ListBox has type IEnumerable (msdn).
So you can't assign to it object of type Worker.
You can create converter to do that.
Converter class:
public class WorkerToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new List<Worker> { value as Worker };
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML code:
...
<Window.Resources>
<local:WorkerToListConverter x:Key="myCon" />
</Window.Resources>
...
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}"
ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem, Converter={StaticResource myCon}}" />
...
You should also delete SelectedItem binding from ComboBox.
... SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}" ...
It's no sense to binding SelectedItem to the same thing as ItemsSource.
I want to bind the combobox's selected value to the bounded object property, and also set the selected index of every combobox to 0.
The problem is that only first combo shows the selected item.
public enum SubEnum1
{
Apple=1,
Banana=2,
Pear=3
}
public enum FullEnum
{
Apple=1,
Banana=2,
Pear=3,
Cucumber=4,
Tomato=5,
Onion=6
}
Im XAML window i have some data control (list) where is a datatemplate that has a combobox.
comboboxes are bounded to SubEnum1.
The data-control is bounded to an object-collection:
List<MyObject> collection = new List<MyObject>()
//collection.Add...
mylist.ItemsSource = collection;
public class MyObject
{
public FullEnum TheSelectedEnum {get;set;}
....
//other properties
}
public class EnumConverter2 : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
return (FullEnum)value;
else return "";
}
}
<ObjectDataProvider x:Key="Enum1"
MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:SubEnum1" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ListBox Height="261" HorizontalAlignment="Left" Name="mylist" VerticalAlignment="Top" Width="278">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ComboBox Height="23" Width="90"
ItemsSource="{Binding Source={StaticResource Enum1}}"
SelectedValue="{Binding Path=TheSelectedEnum, Converter={StaticResource enumConverter}}"
SelectedIndex="0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(If I expand other comboboxes I see the values)
Update to post:
Maybe I can somehow else pass the selected value to the binded object?
You can do this and solve your problem:
public enum SubEnum1
{
None=0,
Apple=1,
Banana=2,
Pear=3
}
Then use FallbackValue:
<ComboBox Height="23" Width="90"
ItemsSource="{Binding Source={StaticResource Enum1}}"
SelectedValue="{Binding Path=TheSelectedEnum, FallbackValue=0}" />
I've bound enums to radio buttons before, and I generally understand how it works. I used the alternate implementation from this question: How to bind RadioButtons to an enum?
Instead of enumerations, I'd like to generate a runtime-enumerated set of a custom type and present those as a set of radio buttons. I have gotten a view working against a runtime-enumerated set with a ListView, binding to the ItemsSource and SelectedItem properties, so my ViewModel is hooked up correctly. Now I am trying to switch from a ListView to a ItemsControl with radio buttons.
Here's as far as I've gotten:
<Window.Resources>
<vm:InstanceToBooleanConverter x:Key="InstanceToBooleanConverter" />
</Window.Resources>
<!-- ... -->
<ItemsControl ItemsSource="{Binding ItemSelections}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ISomeType}">
<RadioButton Content="{Binding Name}"
IsChecked="{Binding Path=SelectedItem, Converter={StaticResource InstanceToBooleanConverter}, ConverterParameter={Binding}}"
Grid.Column="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
InstanceToBooleanConverter has the same implementation as EnumToBooleanConverter from that other question. This seems right, since it seems like it just invokes the Equals method:
public class InstanceToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
The problem I am getting now is that I can't figure out how to send a runtime value as the ConverterParameter. When I try (with the code above), I get this error:
A 'Binding' cannot be set on the 'ConverterParameter' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Is there a way to bind to the item instance, and pass it to the IValueConverter?
It turns out that it is much simpler to abandon using ItemsControl and instead go with ListBox.
It may be more heavy-weight, but that's mostly because it is doing the heavy lifting for you. It is really easy to do a two-way binding between RadioButton.IsChecked and ListBoxItem.IsSelected. With the proper control template for the ListBoxItem, you can easily get rid of all the selection visual.
<ListBox ItemsSource="{Binding Properties}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemContainerStyle>
<!-- Style to get rid of the selection visual -->
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:SomeClass}">
<RadioButton Content="{Binding Name}" GroupName="Properties">
<!-- Binding IsChecked to IsSelected requires no support code -->
<RadioButton.IsChecked>
<Binding Path="IsSelected"
RelativeSource="{RelativeSource AncestorType=ListBoxItem}"
Mode="TwoWay" />
</RadioButton.IsChecked>
</RadioButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As far as I know, there's no good way to do this with a MultiBinding, although you initially think there would be. Since you can't bind the ConverterParameter, your ConvertBack implementation doesn't have the information it needs.
What I have done is created a separate EnumModel class solely for the purpose of binding an enum to radio buttons. Use a converter on the ItemsSource property and then you're binding to an EnumModel. The EnumModel is just a forwarder object to make binding possible. It holds one possible value of the enum and a reference to the viewmodel so it can translate a property on the viewmodel to and from a boolean.
Here's an untested but generic version:
<ItemsControl ItemsSource="{Binding Converter={StaticResource theConverter} ConverterParameter="SomeEnumProperty"}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton IsChecked="{Binding IsChecked}">
<TextBlock Text="{Binding Name}" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The converter:
public class ToEnumModelsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = value;
var prop = viewmodel.GetType().GetProperty(parameter as string);
List<EnumModel> enumModels = new List<EnumModel>();
foreach(var enumValue in Enum.GetValues(prop.PropertyType))
{
var enumModel = new EnumModel(enumValue, viewmodel, prop);
enumModels.Add(enumModel);
}
return enumModels;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The EnumModel:
public class EnumModel : INPC
{
object enumValue;
INotifyPropertyChanged viewmodel;
PropertyInfo property;
public EnumModel(object enumValue, object viewmodel, PropertyInfo property)
{
this.enumValue = enumValue;
this.viewmodel = viewmodel as INotifyPropertyChanged;
this.property = property;
this.viewmodel.PropertyChanged += new PropertyChangedEventHandler(viewmodel_PropertyChanged);
}
void viewmodel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == property.Name)
{
OnPropertyChanged("IsChecked");
}
}
public bool IsChecked
{
get
{
return property.GetValue(viewmodel, null).Equals(enumValue);
}
set
{
if (value)
{
property.SetValue(viewmodel, enumValue, null);
}
}
}
}
For a code sample that I know works (but it's still quite unpolished - WIP!), you can see http://code.google.com/p/pdx/source/browse/trunk/PDX/PDX/Toolkit/EnumControl.xaml.cs. This only works within the context of my library, but it demonstrates setting the Name of the EnumModel based on the DescriptionAttribute, which might be useful to you.
You are so close. When you are need two bindings for one converter you need a MultiBinding and a IMultiValueConverter! The syntax is a little more verbose but no more difficult.
MultiBinding Class
IMultiValueConverter Interface
Edit:
Here's a little code to get you started.
The binding:
<RadioButton Content="{Binding Name}"
Grid.Column="0">
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding Path="SelectedItem"/>
<Binding Path="Name"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
and the converter:
public class EqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].Equals(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Second Edit:
The above approach is not useful to implement two-way binding using the technique linked in the question because the necessary information is not available when converting back.
The correct solution I believe is straight-up MVVM: code the view-model to match the needs of the view. The amount of code is quite small and obviates the need for any converters or funny bindings or tricks.
Here is the XAML;
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
GroupName="Value"
Content="{Binding Description}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and code-behind to simulate the view-model:
DataContext = new CheckBoxValueCollection(new[] { "Foo", "Bar", "Baz" });
and some view-model infrastructure:
public class CheckBoxValue : INotifyPropertyChanged
{
private string description;
private bool isChecked;
public string Description
{
get { return description; }
set { description = value; OnPropertyChanged("Description"); }
}
public bool IsChecked
{
get { return isChecked; }
set { isChecked = value; OnPropertyChanged("IsChecked"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CheckBoxValueCollection : ObservableCollection<CheckBoxValue>
{
public CheckBoxValueCollection(IEnumerable<string> values)
{
foreach (var value in values)
this.Add(new CheckBoxValue { Description = value });
this[0].IsChecked = true;
}
public string SelectedItem
{
get { return this.First(item => item.IsChecked).Description; }
}
}
Now that I know about x:Shared (thanks to your other question), I renounce my previous answer and say that a MultiBinding is the way to go after all.
The XAML:
<StackPanel>
<TextBlock Text="{Binding SelectedChoice}" />
<ItemsControl ItemsSource="{Binding Choices}">
<ItemsControl.Resources>
<local:MyConverter x:Key="myConverter" x:Shared="false" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton>
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource myConverter}" >
<Binding Path="DataContext.SelectedChoice" RelativeSource="{RelativeSource AncestorType=UserControl}" />
<Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</RadioButton.IsChecked>
<TextBlock Text="{Binding}" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The viewmodel:
class Viewmodel : INPC
{
public Viewmodel()
{
Choices = new List<string>() { "one", "two", "three" };
SelectedChoice = Choices[0];
}
public List<string> Choices { get; set; }
string selectedChoice;
public string SelectedChoice
{
get { return selectedChoice; }
set
{
if (selectedChoice != value)
{
selectedChoice = value;
OnPropertyChanged("SelectedChoice");
}
}
}
}
The converter:
public class MyConverter : IMultiValueConverter
{
object selectedValue;
object myValue;
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
selectedValue = values[0];
myValue = values[1];
return selectedValue == myValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return new object[] { myValue, Binding.DoNothing };
}
else
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
}