Listbox IsSelected with SelectionMode=Extended - c#

Sorry for the vague title, I couldn't come up with a good way to summarize what is happening.
I have a bound WPF listbox:
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:MyBoundObject}">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</UserControl.Resources>
<ListBox ItemsSource="{Binding SomeSource}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I want to operate on ONLY the selected items. I do this by iterating through a list of all items and checking each object to see if it's IsSelected property is set.
This works except for when I have many items in the list (enough so they are not all visible) and I press CTRL-A to select all items. When I do this, all the visible items have their IsSelected property set to true, and all the rest are left false. As soon as I scroll down, the other items come into view and their IsSelected properties are then set to true.
Is there any way to fix this behaviour so that every object's IsSelected property is set to true when I press CTRL-A?

Try set the
ScrollViewer.CanContentScroll="False"
on the ListBox, it should fix the ctrl+a problem.

If you want get all selected items you can use SelectedItems property from ListBox. You don't need to add IsSelected property to your object.
Check below example.
XAML file:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Selected items" Click="Button_Click" />
<Button Content="Num of IsSelected" Click="Button_Click_1" />
</StackPanel>
<ListBox Name="lbData" SelectionMode="Extended" Grid.Row="1">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
Code-behind file:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace ListBoxItems
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyBoundObject> _source = new List<MyBoundObject>();
for (int i = 0; i < 100000; i++)
{
_source.Add(new MyBoundObject { Label = "label " + i });
}
lbData.ItemsSource = _source;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(lbData.SelectedItems.Count.ToString());
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
int num = 0;
foreach (MyBoundObject item in lbData.Items)
{
if (item.IsSelected) num++;
}
MessageBox.Show(num.ToString());
}
}
public class MyBoundObject
{
public string Label { get; set; }
public bool IsSelected { get; set; }
}
}

Related

WPF - UserControl constructing performance (very poor)

I have a more complex code on my hand, but to ask this question I am bringing a simpler example of code.
My App is going to iterate throughout all glyphs in a specific font (expected 500 to 5000 glyphs). Each glyph should have a certain custom visual, and some functionality in it. For that I thought that best way to achieve that is to create a UserControl for each glyph.
On the checking I have made, as my UserControl gets more complicated, it takes more time to construct it. Even a simple adding of Style makes a meaningful effect on the performance.
What I have tried in this example is to show in a ListBox 2000 glyphs. To notice the performance difference I put 2 ListBoxes - First is binding to a simple ObservableCollection of string. Second is binding to ObservableCollection of my UserControl.
This is my MainWindow xaml:
<Grid Background="WhiteSmoke">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ListBox Margin="10" ItemsSource="{Binding MyCollection}"></ListBox>
<ListBox Margin="10" Grid.Row="1" ItemsSource="{Binding UCCollection}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"></ListBox>
</Grid>
On code behind I have 2 ObservableCollection as mentioned:
public static ObservableCollection<string> MyCollection { get; set; } = new ObservableCollection<string>();
public static ObservableCollection<MyUserControl> UCCollection { get; set; } = new ObservableCollection<MyUserControl>();
For the first List of string I am adding like this:
for (int i = 0; i < 2000; i++)
{
string glyph = ((char)(i + 33)).ToString();
string hex = "U+" + i.ToString("X4");
MyCollection.Add($"Index {i}, Hex {hex}: {glyph}");
}
For the second List of MyUserControl I am adding like this:
for (int i = 0; i < 2000; i++)
{
UCCollection.Add(new MyUserControl(i + 33));
}
MyUserControl xaml looks like this:
<Border Background="Black" BorderBrush="Orange" BorderThickness="2" MinWidth="80" MinHeight="80">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}"/>
<TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1"/>
<TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2"/>
</Grid>
</Border>
And code behind of MyUserControl:
public partial class MyUserControl : UserControl
{
private int OrgIndex { get; set; } = 0;
public string Hex => "U+" + OrgIndex.ToString("X4");
public string Index => OrgIndex.ToString();
public string Glyph => ((char)OrgIndex).ToString();
public MyUserControl(int index)
{
InitializeComponent();
OrgIndex = index;
}
}
In order to follow the performance issue I have used Stopwatch. Adding 2000 string items to the first list took 1ms. Adding 2000 UserControls to the second list took ~1100ms. And it is just a simple UserControl, when I add some stuff to it, it takes more time and performance getting poorer. For example if I just add this Style to Border time goes up to ~1900ms:
<Style TargetType="{x:Type Border}" x:Key="BorderMouseOver">
<Setter Property="Background" Value="Black" />
<Setter Property="BorderBrush" Value="Orange"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="80"/>
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" Value="True">
<Setter Property="Background" Value="#FF2A3137" />
<Setter Property="BorderBrush" Value="#FF739922"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
I am not fully familiar with WPF work around, so I will really appreciate your help. Is this a totally wrong way to do this? I have read some posts about it, but could not manage to go through this: here, and here, and here and here and more.
This example full project can be downloaded Here
For your case, you can create DependencyProperty in your user control like so (just an example).
#region DP
public int OrgIndex
{
get => (int)GetValue(OrgIndexProperty);
set => SetValue(OrgIndexProperty, value);
}
public static readonly DependencyProperty OrgIndexProperty = DependencyProperty.Register(
nameof(OrgIndex), typeof(int), typeof(MyUserControl));
#endregion
And other properties can be set as DP or handle in init or loaded event...
Then use your usercontrol in listbox as itemtemplate...
<ListBox
Grid.Row="1"
Margin="10"
ItemsSource="{Binding IntCollection}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyUserControl OrgIndex="{Binding Path=.}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in your vm, create simple type list
public static ObservableCollection<int> IntCollection { get; set; } = new ObservableCollection<int>();
for (int i = 0; i < rounds; i++)
{
IntCollection.Add(i + 33);
}
It's quite faster than create a usercontrol list, and you can have your usercontrol and its style as a listviewitem
What solve this issue for now, is following #Andy suggestion to use MVVM approach. It was a bit complicated for me, and had to do some learning around.
What I did:
Cancaled the UserControl.
Created a class GlyphModel. That represents each glyph and it's information.
Created a class GlyphViewModel. That builds an ObservableCollection list.
Set the design for the GlyphModel as a ListBox.ItemTemplate.
So now GlyphModel class, implants INotifyPropertyChanged and looks like this:
public GlyphModel(int index)
{
_OriginalIndex = index;
}
#region Private Members
private int _OriginalIndex;
#endregion Private Members
public int OriginalIndex
{
get { return _OriginalIndex; }
set
{
_OriginalIndex = value;
OnPropertyChanged("OriginalIndex");
}
}
public string Hex => "U+" + OriginalIndex.ToString("X4");
public string Index => OriginalIndex.ToString();
public string Glyph => ((char)OriginalIndex).ToString();
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Members
And GlyphViewModel class looks like this:
public static ObservableCollection<GlyphModel> GlyphModelCollection { get; set; } = new ObservableCollection<GlyphModel>();
public static ObservableCollection<string> StringCollection { get; set; } = new ObservableCollection<string>();
public GlyphViewModel(int rounds)
{
for (int i = 33; i < rounds; i++)
{
GlyphModel glyphModel = new GlyphModel(i);
GlyphModelCollection.Add(glyphModel);
StringCollection.Add($"Index {glyphModel.Index}, Hex {glyphModel.Hex}: {glyphModel.Glyph}");
}
}
In the MainWindow XML I have defined the list with DataTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource BorderMouseOver}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center" Foreground="White" FontSize="40" Text="{Binding Glyph}" />
<TextBlock HorizontalAlignment="Center" Foreground="OrangeRed" Text="{Binding Index}" Grid.Row="1" />
<TextBlock HorizontalAlignment="Center" Foreground="White" Text="{Binding Hex}" Grid.Row="2" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
And for last set the DataContext for the MainWindow:
DataContext = new GlyphViewModel(2000);
It does work, and works very fast even for 4000 glyphs. Hope this is the right way for doing that.

How to write the seats for a booking application in WPF?

I'm using right now this:
<Window.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<Button Content="{Binding}" Height="40" Width="50" Margin="4,4,4,4"/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
With this:
<ItemsControl
x:Name="lst"
ItemTemplate="{DynamicResource DataTemplate_Level1}"
ItemsSource="{Binding TopSeats}"/>
I'm binding a List< List< int?>> as itemssource with numbers 0-2. 0 for empty, 1 for selected, 2 for booked.
List<List<int?>> topSeats;
public List<List<int?>> TopSeats
{
get => topSeats;
set
{
topSeats = value;
NotifyPropertyChanged("TopSeats");
}
}
My UI looks like this right now:
enter image description here
When i press a button, it should change from 0 to 1, and the corresponding element in the List< List< int?>> container should change too.
But i've arrived to a brick wall. I have no idea how to make sure that, when i press any button, the correct element changes in the "List< List< int?>>" container.
Is it possible somehow without code behind?
Its a big process to explain everything here. But I'll try my best to give you a working solution and hope you can read more about INotifyPropertyChanged, MVVM pattern and ICommand pattern.
For simplicity, I have not implemented ICommand here and using a code-behind click to get the selected seat numbers (This is only for testing to see if selected seat numbers are able to retrieve or not).
Step 1: I have created a Model class called Seat with following properties and I am implementing INotifyPropertyChanged interface to capture property changed events. See below for my Seat.cs class.
public class Seat : INotifyPropertyChanged
{
private int seatNo;
private string seatNumber;
private bool isSelected;
public int SeatNo
{
get { return seatNo; }
set { seatNo = value; OnPropertyChanged(); }
}
public string SeatNumber
{
get { return seatNumber; }
set { seatNumber = value; OnPropertyChanged(); }
}
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value; OnPropertyChanged(); }
}
public void OnPropertyChanged([CallerMemberName]string popertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(popertyName));
}
private void BaseVM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
public event PropertyChangedEventHandler PropertyChanged;
}
Step 2:
I have modified "DataTemplate_Level2" template to have a CheckBox instead of a Button. Because I wanted to get the selected behavior where CheckBox has it. See below for the modified "DataTemplate_Level2"
<DataTemplate x:Key="DataTemplate_Level2" DataType="{x:Type local:Seat}">
<CheckBox Content="{Binding SeatNumber}" Height="40" Width="50" Margin="4" Style="{StaticResource CheckBoxStyle}"
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
Step 3: I have modified appearance of the CheckBox. So that it does not appear like a checkbox but looks like a button (you can still customize to look like a real seat). See below for my modified CheckBoxStyle
<Style x:Key="CheckBoxStyle" TargetType="CheckBox">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Border x:Name="MainBorder" BorderBrush="Red" BorderThickness="1">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=CheckBox}, Path=Content}"
TextAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="MainBorder" Property="Background" Value="Yellow" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Step 4: I have added some buttons and textblock to my Window to test for the selected seats (This piece of code is for testing purpose only). See below my rest of the xaml.
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}" ItemsSource="{Binding TopSeatList}"/>
<Button Content="Get Selected Seat Numbers" Height="40" Width="50" Margin="4,4,4,4" Grid.Row="1" Click="Button_Click"/>
<TextBlock x:Name="SelectedSeatNumbersTextBlock" Grid.Row="2" />
</Grid>
Note:- There is no modification for "DataTemplate_Level1". Hence you can still copy-paste of yours.
Step 5: Now in my main window view model, I have added a list like your List< List > and populated some dummy data.
public List<List<Seat>> TopSeatList
{
get => topSeatList;
set
{
topSeatList = value;
OnPropertyChanged("TopSeatList");
}
}
Step 6: In the code-behind for the Button_Click event I did to get the selected seat numbers and display in a textblock.
private void Button_Click(object sender, RoutedEventArgs e)
{
var selectedSeats = selectSeatsViewModel.TopSeatList.SelectMany(x => x.Where(y => y.IsSelected));
string selectedSeatNumbers = string.Empty;
foreach(var seat in selectedSeats)
{
selectedSeatNumbers += seat.SeatNumber + "";
}
SelectedSeatNumbersTextBlock.Text = selectedSeatNumbers;
}
Note:- I consider to implement much better approach above click event with a command so that you can avoid it writing it in code-behind.
I hope this helps you to move forward with your solution. Please give a try and let us know results. Feel free to post your questions.

How to have a dynamic DataTemplateSelector

I have an observable collection that I am displaying in a Xamarin Forms ListView. I have defined a detail and a summary template that I use to view each list item. I want to be able to dynamically change between summary and detail template based on a Boolean property in each item.
Here is the item.
public class MyItem : INotifyPropertyChanged
{
bool _switch = false;
public bool Switch
{
get
{
return _switch;
}
set
{
if (_switch != value)
{
_switch = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Switch"));
}
}
}
public int Addend1 { get; set; }
public int Addend2 { get; set; }
public int Result
{
get
{
return Addend1 + Addend2;
}
}
public string Summary
{
get
{
return Addend1 + " + " + Addend2 + " = " + Result;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is the observable collection. Note that whenever the switch value changes I remove the item and reinsert. The reason this is done is to force the ListView to reselect the DataTemplate.
public class MyItems : ObservableCollection<MyItem>
{
protected override void InsertItem(int index, MyItem item)
{
item.PropertyChanged += MyItems_PropertyChanged;
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
this[index].PropertyChanged -= MyItems_PropertyChanged;
base.RemoveItem(index);
}
private void MyItems_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
int index = IndexOf(sender as MyItem);
if(index >= 0)
{
RemoveAt(index);
Insert(index, sender as MyItem);
}
}
}
Here is my data template selector...
public class MyItemTemplateSelector : DataTemplateSelector
{
DataTemplate Detail { get; set; }
DataTemplate Summary { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if(item is MyItem)
{
return (item as MyItem).Switch ? Detail : Summary;
}
return null;
}
}
Here are my resource definitions...
<DataTemplate x:Key="MyDetail">
<ViewCell>
<StackLayout Orientation="Horizontal">
<Switch IsToggled="{Binding Switch}"/>
<Entry Text="{Binding Addend1}"/>
<Entry Text="{Binding Addend2}"/>
<Label Text="{Binding Result}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="MySummary">
<ViewCell>
<StackLayout Orientation="Horizontal">
<Switch IsToggled="{Binding Switch}"/>
<Label Text="{Binding Summary}" VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
<local:MyItemTemplateSelector x:Key="MySelector" Detail="{StaticResource MyDetail}" Summary="{StaticResource MySummary}"/>
Here is my collection initialization...
MyItems = new MyItems();
MyItems.Add(new MyItem() { Switch = true, Addend1 = 1, Addend2 = 2 });
MyItems.Add(new MyItem() { Switch = false, Addend1 = 1, Addend2 = 2 });
MyItems.Add(new MyItem() { Switch = true, Addend1 = 2, Addend2 = 3 });
MyItems.Add(new MyItem() { Switch = false, Addend1 = 2, Addend2 = 3 });
And this is what it looks like...
Right. So everything works fine. If the switch is toggled the view of the item changes from summary to detail. The problem is that this cannot be the right way of doing this! It is a complete kluge to remove a list item and put it back in the same place in order to get the data template to reselect. But I cannot figure out another way of doing it. In WPF I used a data trigger in an item container style to set the content template based on the switch value, but there seems to be no way to do the equivalent thing in Xamarin.
The way to do this is not through switching templates, but defining a content view as the template and changing the visibility of controls within the template. There is apparently no way to get the ListView to re-evaluate the item template on an item short of removing it and re-adding it.
Here is my content view...
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinFormsBench"
x:Class="XamarinFormsBench.SummaryDetailView">
<ContentView.Content>
<StackLayout x:Name="stackLayout" Orientation="Horizontal">
<Switch x:Name="toggle" IsToggled="{Binding Switch}"/>
<Entry x:Name="addend1" Text="{Binding Addend1}"/>
<Entry x:Name="addend2" Text="{Binding Addend2}"/>
<Label x:Name="result" Text="{Binding Result}"/>
<Label x:Name="summary" Text="{Binding Summary}" VerticalOptions="Center"/>
</StackLayout>
</ContentView.Content>
This is the code behind...
namespace XamarinFormsBench
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SummaryDetailView : ContentView
{
public SummaryDetailView()
{
InitializeComponent();
toggle.PropertyChanged += Toggle_PropertyChanged;
UpdateVisibility();
}
private void Toggle_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "IsToggled")
{
UpdateVisibility();
}
}
private void UpdateVisibility()
{
bool isDetail = toggle.IsToggled;
addend1.IsVisible = isDetail;
addend2.IsVisible = isDetail;
result.IsVisible = isDetail;
summary.IsVisible = !isDetail;
InvalidateLayout(); // this is key!
}
}
}
Now the main page contains this...
<ListView ItemsSource="{Binding MyItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:SummaryDetailView/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The key to making this work properly is to invalidate the layout of the ContentView when switching between summary and detail. This forces the ListView to layout the cell again. Without this the controls that are made invisible disappear the controls made visible never show. You do not need this if the ContentView is used outside of the ListView. This seems to me to be a bug in the ListView. You could get the item template switching to work if you could invalidate the layout of the ViewCell, but there is no public method (only a protected one) to do this.
This was tricky issue for me few years ago. I've came to MarkupExtensions and converters (IValueConverter). After heavy struggle with XAML extensions realm I've figured an obvious thing: it shouldn't be done like that.
For dynamic change of (m)any property(ies) of the component you should use Styles. Reactions of property (it has to be DependencyProperty to work with components) changes are simple to set via Stryle.Triggers and Setters.
<Style x:Key="imbXmlTreeView_itemstyle" TargetType="TreeViewItem">
<Setter Property="Margin" Value="-23,0,0,0" />
<Setter Property="Padding" Value="1" />
<Setter Property="Panel.Margin" Value="0"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource fade_lightGray}" />
<Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Background" Value="{DynamicResource fade_lightGray}" />
<Setter Property="Foreground" Value="{DynamicResource fade_darkGray}" />
</Trigger>
</Style.Triggers>
</Style>
Consider above (just copied from my old project): DynamicResource can be your DataTemplate.
Here is more accurate example you might use:
<Style x:Key="executionFlowBorder" TargetType="ContentControl" >
<Setter Property="Margin" Value="5" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border Style="{DynamicResource executionBorder}" DataContext="{Binding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding Path=isExecuting}" Content="" Grid.Column="0" VerticalAlignment="Center"/>
<Label Content="{Binding Path=displayName, Mode=OneWay}" FontSize="10" Grid.Column="1" FontStretch="Expanded" FontWeight="Black"/>
<Image Source="{Binding Path=iconSource, Mode=OneWay}" Width="16" Height="16" Grid.Column="2" HorizontalAlignment="Right" Margin="0,0,5,0"/>
</Grid>
</Border>
<Label Content="{Binding Path=displayComment, Mode=OneWay}" FontSize="9" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Where the value of setter can be DynamicResource or one delivered via your MarkupExtension - some thing like I had here:
using System;
using System.Windows;
using System.Windows.Markup;
#endregion
/// <summary>
/// Pristup glavnom registru resursa
/// </summary>
[MarkupExtensionReturnType(typeof (ResourceDictionary))]
public class masterResourceExtension : MarkupExtension
{
public masterResourceExtension()
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
try
{
return imbXamlResourceManager.current.masterResourceDictionary;
}
catch
{
return null;
}
}
}
The MarkupExtensions you are using as in example below:
In the XAML code:
<Image Grid.Row="1" Name="image_splash" Source="{imb:imbImageSource ImageName=splash}" Stretch="Fill" />
Added later: just don't forget to add namespace/assembly reference (pointing to the code with the custom MarkupExtension) at top of the XAML Window/Control (in this example it is imbCore.xaml from separate library project of the same solution):
<Window x:Class="imbAPI.imbDialogs.imbSplash"
xmlns:imb="clr-namespace:imbCore.xaml;assembly=imbCore"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Path=splashTitle}" Height="666" Width="896" ResizeMode="NoResize" WindowStyle="ToolWindow" Topmost="False" WindowStartupLocation="CenterScreen"
xmlns:imbControls="clr-namespace:imbAPI.imbControls">
<Grid>
Also have in mind you have to compile it first in order to get it working in XAML designer.
The C# code of the extension used:
using System;
using System.Windows.Markup;
using System.Windows.Media;
using imbCore.resources;
#endregion
[MarkupExtensionReturnType(typeof (ImageSource))]
public class imbImageSourceExtension : MarkupExtension
{
public imbImageSourceExtension()
{
}
public imbImageSourceExtension(String imageName)
{
this.ImageName = imageName;
}
[ConstructorArgument("imageName")]
public String ImageName { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
try
{
if (imbCoreApplicationSettings.doDisableIconWorks) return null;
return imbIconWorks.getIconSource(ImageName);
}
catch
{
return null;
}
}
}
Hope I got your question right on the first place :).
Now I have to sleap :). Good luck!
Added later: ok, I missed your point :) sorry. However, I would leave the response in case you find something useful in the codes I've posted. Bye!

A master-detail TabControl binding (1:n) using parent SelectedItem with ICommand

I have XAML related question I have tried to research an answer in vain. I have commented the relevant questions to the XAML. It looks to me this questions is a more complex because of the way I try to arrange things.
Basically I have a main view model used in the TabControl headers and then in the content area I would show a list of items from the main view model. I just don't know how to to the binding. This is the main question. However, I suspect the next and ultimate objectives I have might factor in how to think about this, so I added them too. The rest of the code is for the sake of completeness.
<StackPanel>
<TabControl x:Name="mainsTabControl"
IsSynchronizedWithCurrentItem="True"
IsEnabled="True"
Visibility="Visible"
ItemsSource="{Binding Path=Mains}">
<!-- How to select a different background for the selected header? Note that the background color is "selected tab" if MainContentViewModel.IsActive is not TRUE.
If it is, a different color is chosen. Here this fact is just emulated with IsEnabled property due to well, multi-binding to the rescue (and a converter)? -->
<!-- Is there a clean way to use ICommand binding (RelayCommand) to check if it is OK to change the tab and if necessary, present a dialogue asking for the change? -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsActive}"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- This binds to every item in the MainViewModel.Mains collection. Note the question about background color. -->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- This binding gives reference to the selected MainContentViewModel. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="10" DataContext="{Binding ElementName=mainsTabControl, Path=SelectedItem, Mode=OneWay}">
<ItemsControl ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Linq;
namespace WpfDependencyInjection.ViewModel
{
public class MainContentViewModel: ViewModelBase
{
private ObservableCollection<CustomItemViewModel> customItems;
private mainContentDto MainContent { get; set; }
public string Name { get; }
public bool isActive;
public MainContentViewModel(Engine engine, mainContentDto mainContent)
{
MainContent = mainContent;
Name = MainContent.Name;
IsActive = true;
//The custom items belonging to this main content.
var customItems = engine.CustomItemContents.Where(i => i.MainContentId == MainContent.Id).Select(i => new CustomItemViewModel(engine, i));
CustomItems = new ObservableCollection<CustomItemViewModel>(customItems);
}
public ObservableCollection<CustomItemViewModel> CustomItems
{
get
{
return customItems;
}
set
{
customItems = value;
RaisePropertyChanged(nameof(CustomItems));
}
}
public bool IsActive
{
get
{
return isActive;
}
private set
{
isActive = value;
RaisePropertyChanged(nameof(IsActive));
}
}
}
}
public class CustomItemViewModel: ViewModelBase
{
private Engine Engine { get; }
private ItemTypeDto CustomItem { get; set; }
public string Name { get; }
public CustomItemViewModel(Engine engine, ItemTypeDto customItem)
{
Engine = engine;
CustomItem = customItem;
Name = customItem.Name;
}
}
namespace WpfDependencyInjection
{
public class Engine
{
public string Name { get; } = "EngineMan";
public List<mainContentDto> MainContents { get; set; } = new List<mainContentDto>(new[]
{
new mainContentDto { Name = "Main One", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Version = 1 },
new mainContentDto { Name = "Main Two", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Version = 1 }
});
public List<ItemTypeDto> CustomItemContents { get; set; } = new List<ItemTypeDto>(new ItemTypeDto[]
{
new ItemType1Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType1Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType2Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Name = "ItemType2Dto 2", Id = Guid.NewGuid(), Version = 1 }
});
public Engine()
{
}
}
}
<edit: The binding solved partially, though not the ICommand part.
Try this:
<TabControl x:Name="mainsTabControl"
IsEnabled="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Mains}"
SelectedItem="0"
Visibility="Visible">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="HotPink" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<Button Background="{x:Null}"
Command="{Binding SomeCommand}"
Content="{Binding Name}"
FocusVisualStyle="{x:Null}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<ItemsControl Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CustomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Command:
There may be cleaner ways but here we bind IsSelected for the TabItem to the IsSelected property of the viewmodel. This enables having a command that asks if it is ok to navigate and sets IsSelected to true if it is.
Background:
We also retemplate the tabitem so that background works as we want. If you check with Snoop WPF inserts an extra border when the item is selected.
Side note 1:
Don't put the TabControl into a StackPanel like that. A StackPanel sizes to content and will kill scrolling and draw outside the control. Also it comes with a cost, a deep visual tree is not cheap. Same in the ItemTemplate and the other places. In fact StackPanel is rarely right for anything :)
Side note 2:
If you specify DataType in your DataTemplate you get intellisense and some compiletime checking.

Updating a ListBox with different Content On Button Clicks in WPF

So I have a listbox and a tool bar in my WPF app. The tool bar just has regular controls, and the listbox has vertical expanders.
I need the listbox to have a different set of expanders depending on what button is clicked. Right now it looks like such:
<ListBox>
<local:Select_Analysis_Panel/>
</ListBox>
Where local:Select_Analysis_Panel is seperate user control file containing the expanders. What is the best way to go about dynamically updating the ListBox control's content upon a button click?
For the last couple hours I've been trying to use set DataTemplates for each expander set and bind the to the items control property with little avail with the code below. I'm just trying to get basic framework laid out before setting up a MVVM interface. Later on I was going to replace the ItemsSource="Network_anal" with you know ItemsSource="{Binding WhatExpanderViewModelProperty}" or something like that.
<ListBox Width="250" Margin="5,0,0,0">
<ListBox.Resources>
<DataTemplate DataType="Select_Analysis_Panel">
<local:Select_Analysis_Panel/>
</DataTemplate>
<DataTemplate x:Key="Network_anal" DataType="NetworkAnalysis">
<local:NetworkAnalysis/>
</DataTemplate>.Resources>
<ListBox.Template>
<ControlTemplate>
<Border Background="Red"/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl ItemsSource="Network_anal"/>
</ListBox>
Am I taking the right approach to this at all?
Here's what I'm trying to do. Below when the "File" button is clicked the side bar displays these 2 expanders:
And when "Network Design" button these expanders are dipslayed:
Option 1:
Subclassing the sections:
each of these sections could be subclassed from a base section class and a specific DataTemplate could be used for each:
<Window x:Class="MiscSamples.MultiToolbar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MultiToolbar" Height="300" Width="300">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Sections}"
SelectedItem="{Binding SelectedSection}"
DisplayMemberPath="Name"
DockPanel.Dock="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderBrush="Black" BorderThickness="1">
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter ContentSource="Content"/>
</ToggleButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ScrollViewer Width="300" DockPanel.Dock="Left">
<ContentPresenter Content="{Binding SelectedSection}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:FileSection}">
<TextBlock Text="User Control For File Section"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:NetworkDesignSection}">
<TextBlock Text="User Control For Network Design"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectAnalysisSection}">
<TextBlock Text="User Control For Select Analysis"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</ScrollViewer>
<Grid Background="Gray">
<TextBlock Text="Design Surface" TextAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class MultiToolbar : Window
{
public MultiToolbar()
{
InitializeComponent();
var vm = new MainViewModel();
vm.Sections.Add(new FileSection() {Name = "File"});
vm.Sections.Add(new NetworkDesignSection() { Name = "Network Design" });
vm.Sections.Add(new SelectAnalysisSection() { Name = "Select Analysis" });
DataContext = vm;
}
}
Main ViewModel:
public class MainViewModel: PropertyChangedBase
{
private ObservableCollection<Section> _sections;
public ObservableCollection<Section> Sections
{
get { return _sections ?? (_sections = new ObservableCollection<Section>()); }
}
private Section _selectedSection;
public Section SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
OnPropertyChanged("SelectedSection");
}
}
}
Sections:
public abstract class Section:PropertyChangedBase
{
public string Name { get; set; }
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//Optionally
//public string ImageSource {get;set;}
//ImageSource = "/Resources/MySection.png";
}
public class FileSection: Section
{
///... Custom logic specific to this Section
}
public class NetworkDesignSection:Section
{
///... Custom logic specific to this Section
}
public class SelectAnalysisSection: Section
{
///... Custom logic specific to File Section
}
//...etc etc etc
Result:
Notice that I'm using ToggleButtons bound to the ListBoxItem.IsSelected property to simulate a TabControl-like behavior.
You can set the DataContext of the whole form and bind the ItemsSource of the listbox, or set ItemsSource of the listbox to some collection directly.

Categories

Resources