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.
Related
I have a combobox with names in it. I have the box set to editable so that the user can enter a name. I want it so that the user can only enter a name that is already in the list.
When the user clicks save I want the box to have the red validation border show up if the box is empty or not in the list.
Is there a way to do this?
<ComboBox IsEditable="True"
Grid.Column="2"
Grid.Row="1"
Margin="5,3,0,0"
Text="{Binding Model.Number}"
ItemsSource="{Binding DList}"
SelectedItem="{Binding Model.Number}"
IsEnabled="{Binding EnableComboBox}"
VerticalAlignment="Top">
</ComboBox>
If I understood correctly, you want the user to be able to select an existing list item by typing, but not type a string that is not on the list. That can be done with the following:
<ComboBox IsEditable="False"></ComboBox>
This will allow the user to start typing the string, but you lose the textbox for input.
Another way to do it is to allow the user to type whatever they want by setting <ComboBox IsReadOnly="False" IsEditable="True"> and handle for example the LostFocus event to check if the input is valid. Here's an example:
private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
{
bool allowed = false;
foreach (ComboBoxItem it in comboBox.Items)
{
if (it.Content.ToString() == comboBox.Text)
{
allowed = true;
break;
}
}
if (!allowed)
{
MessageBox.Show("MISS!");
}
else
{
MessageBox.Show("HIT!");
}
}
For some reason I wasn't able to set the border color quickly, but you get the point from here. Also depending on your ComboBoxItem type, you may need to match the comboBox.Text to a certain property.
Let's assume you use MVVM (it's not what you're doing now) and that
ItemsSource="{Binding DList}"
is a correct binding to a collection of Models
You'd need a
DisplayMemberPath="Number"
Back to your question.
First, let's write another binding for the selected Text
Text="{Binding Selected, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors =true, NotifyOnValidationError=true}"
and implement the validation tooltip inside the combo
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
and the style in the window resources
<Window.Resources>
<Style TargetType="{x:Type Label}">
<Setter Property="Margin" Value="5,0,5,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,40,2" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Finally, we've to validate the ViewModel property
We can project the model list to its numbers for the error check
public class VM : IDataErrorInfo
{
public string this[string columnName]
{
get
{
if (columnName.Equals( "Selected"))
{
if (!DList.Select(m => m.Number).Contains(Selected))
return "Selected number must be in the combo list";
}
return null;
}
}
You can learn more about data validation in MVVM for example here
Fire the validation later
Say you want fire the validation after a save button is clicked
<Button Content="Save"
Command="{Binding SaveCmd}"
you simply need to raise the property changed in the corresponding delegate command
public class VM : ViewModelBase, IDataErrorInfo
{
private bool showValidation;
private int selected;
public int Selected
{
get { return selected; }
set
{
selected = value;
showValidation = true;
OnPropertyChanged("Selected");
}
}
DelegateCommand saveCmd;
public ICommand SaveCmd
{
get
{
if (saveCmd == null)
{
saveCmd = new DelegateCommand(_ => RunSaveCmd(), _ => CanSaveCmd());
}
return saveCmd;
}
}
private bool CanSaveCmd()
{
return true;
}
private void RunSaveCmd()
{
showValidation = true;
OnPropertyChanged("Selected");
}
and exit from the validation before you want to show it.
public string this[string columnName]
{
get
{
if (!showValidation)
{
return null;
}
I have come across a peculiar issue I have had trouble fixing. Below is simplified code that shows the problem.
I have a WPF DropDownButton that contains a list of strings in it's drop down. I also have a search box. The idea being as you type in the search box, the drop down automatically expands and shows only the matching items (thus making it easier for you to find the one you are interested in). If there are no matching items or the search field is empty the drop down is closed. If there is a search term but no matching items the search field changes colour.
All this works fine. Until that is, you either clear the search field (when there was something to clear) using it's button or drop down the list using it's drop down arrow button. Once you do either of these the drop down no longer automatically opens and closes as you change the search term.
Once it is 'broken' the searching still works in that you can search for something e.g. 'Text 1' and if you open the drop down list it contains only the matching items. It is just the automatic opening and closing that no longer works.
I have checked and the ViewModel is raising the correct events. I have looked in to the DropDownButton code and can see it when it works the OnIsOpenChanged code is reached, but once broken it doesn't.
Could it be that by 'manually' opening the drop down I break/overwrite the IsOpen binding? If so how can I work round this. And why does clearing the search field break the binding as well? Removing the manual dropdown aspect is not an option.
EDIT
In the button click code behind (which I have tried replacing with a command but it make no difference to the behaviour) after setting the SeachExpression I added:
BindingExpression be = BindingOperations.GetBindingExpression(SelectButton, Xceed.Wpf.Toolkit.DropDownButton.IsOpenProperty);
which I think is the correct way to check if there is a binding and it comes back null. If I do this before setting the SeachExpression it is non-null.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<string> originalText;
public string SearchExpression
{
get
{
return _searchExpression;
}
set
{
if (value == _searchExpression)
return;
_searchExpression = value;
UpdateDescriptions(_searchExpression);
OnPropertyChanged("SearchExpression");
}
}
string _searchExpression = string.Empty;
public ReadOnlyObservableCollection<string> Descriptions
{
get { return _descriptions; }
private set
{
if (value == _descriptions)
return;
_descriptions = value;
OnPropertyChanged("Descriptions");
}
}
ReadOnlyObservableCollection<string> _descriptions;
public bool NoMatches
{
get { return _noMatches; }
private set
{
if (value == _noMatches)
return;
_noMatches = value;
OnPropertyChanged("NoMatches");
}
}
bool _noMatches;
public bool ShowSearchResults
{
get { return _showSearchResults; }
private set
{
if (value == _showSearchResults)
return;
_showSearchResults = value;
OnPropertyChanged("ShowSearchResults");
}
}
bool _showSearchResults;
public ViewModel()
{
originalText = new List<string>() {"Text 1", "Text 2", "Text 3"};
UpdateDescriptions();
}
private void UpdateDescriptions(string searchExpression = null)
{
ObservableCollection<string> descriptions = new ObservableCollection<string>();
IEnumerable<string> records;
if (string.IsNullOrWhiteSpace(searchExpression))
{
records = originalText;
NoMatches = false;
ShowSearchResults = false;
}
else
{
records = originalText.Where(x => x.Contains(searchExpression));
NoMatches = records.Count() < 1;
ShowSearchResults = records.Count() > 0;
}
descriptions = new ObservableCollection<string>(records);
this.Descriptions = new ReadOnlyObservableCollection<string>(descriptions);
}
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="clr-namespace:Xceed.Wpf.Toolkit;assembly=WPFToolkit.Extended"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="65" Width="225">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<wpf:DropDownButton Grid.Column="0" x:Name="SelectButton" Content="Select" Margin="3 0" IsOpen="{Binding ShowSearchResults, UpdateSourceTrigger=PropertyChanged}">
<wpf:DropDownButton.DropDownContent>
<ListBox x:Name="descriptionsList" MaxHeight="250" ItemsSource="{Binding Path=Descriptions, Mode=OneWay}" HorizontalContentAlignment="Stretch">
<!-- this code makes sure the ListBox displays at the correct with for the outset, and does not resize as you scroll-->
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="True">
<Setter Property="Control.Background" Value="{x:Static SystemColors.HighlightBrush}" />
<Setter Property="Control.Foreground" Value="{x:Static SystemColors.HighlightTextBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</wpf:DropDownButton.DropDownContent>
</wpf:DropDownButton>
<Border Grid.Column="2" Background="White" BorderBrush="Gray" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<xctk:WatermarkTextBox x:Name="searchTextBox" Text="{Binding SearchExpression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Watermark="Search" ToolTip="Search for an item in the list." >
<xctk:WatermarkTextBox.Style>
<Style>
<Setter Property="Control.BorderThickness" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding NoMatches}" Value="True" >
<Setter Property="TextBox.Background" Value="Tomato"/>
<Setter Property="TextBox.Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>
</Style>
</xctk:WatermarkTextBox.Style>
</xctk:WatermarkTextBox>
<Button Grid.Column="1"
x:Name="ClearButton"
ToolTip="Clear search text"
Click="ClearButton_Click"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Source="/WPFToolkit.Extended;component/PropertyGrid/Images/ClearFilter16.png" Width="16" Height="16" />
</Button>
</Grid>
</Border>
</Grid>
View Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
ViewModel vm = this.DataContext as ViewModel;
if (vm != null)
vm.SearchExpression = "";
}
}
I'm seriously stuck with a trouble updating the bound data in WPF.
Basically, i've got some data in my app, which doesn't update when properties change and i can't fix it.
In my app i'm trying to use the MVVM pattern, so there is a Model with properties X, Y:
public class Сross : INotifyPropertyChanged
{
private double x;
private double y;
public double X
{
get { return x; }
set
{
x = value;
RaisePropertyChanged("X");
}
}
public double Y
{
get { return y; }
set
{
y = value;
RaisePropertyChanged("Y");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
...there is a ViewModel, which contains an ObservableCollection of Crosses:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
Crosses = new ObservableCollection<Cross>()
{
new Cross ()
{
X = 300,
Y = 200
},
new Cross ()
{
X = 400,
Y = 300
}
};
private ObservableCollection<Cross> crosses;
public ObservableCollection<Cross> Crosses
{
get
{
return crosses;
}
set
{
crosses = value;
RaisePropertyChanged("Crosses");
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
...and there is a View, where there is a ListBox, databound to this ObservableCollection of Crosses.
<Page x:Class="CharMaker.App.MainPageView">
<Page.DataContext>
<local:MainWindowViewModel />
</Page.DataContext>
....
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" Background="#01FFFFFF" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemsSource>
<Binding Path="Crosses" Mode="TwoWay" />
</ListBox.ItemsSource>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X, Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Y, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Page>
This code works so that when you move the Cross item (which has a DataTemplate based on Thumb control) in a ListBox field, it's Canvas.Left(or Top) is changing and it updates the X and Y properties of a Cross item, and the other way around.
<DataTemplate DataType="{x:Type model:Cross}">
<Thumb DragDelta="Thumb_DragDelta"
IsEnabled="{Binding IsSelected,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Canvas Margin="0">
<Line X1="-5" X2="5" Y1="-5" Y2="5" Stroke="#FF2E61AB" StrokeThickness="1.5"
x:Name="FirstLine" />
<Line X1="-5" X2="5" Y1="5" Y2="-5" Stroke="#FF2E61AB" StrokeThickness="1.5"
x:Name="SecondLine" />
</Canvas>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="FirstLine" Property="Stroke" Value="Red"/>
<Setter TargetName="SecondLine" Property="Stroke" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Thumb.Template>
</Thumb>
</DataTemplate>
The problem is that after listbox is loaded (and Crosses appear on screen in the appropriate positions), they don't change the X and Y properies in the ViewModel as i move them. And if i change X and Y in ObservableCollection Crosses, they don't move on screen.
It's worth mentioning, that i did a ListBox with TextBoxes for testing, which is also bound to X, Y properties of Crosses collection:
<ListBox Grid.Row="2" Grid.ColumnSpan="4" ItemsSource="{Binding Crosses}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Cross}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding X, Mode=TwoWay}" />
<TextBox Grid.Column="1" Text="{Binding Y, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
and the funny thing is that in this ListBox the Data Binding works just fine!
Values are changing in TextBoxes when i move crosses, and crosses move to positions when the value in TextBox is changed.
But in the very same time (if i pause the app) i see that the values in Observable Collection Crosses in ViewModel are same as they were when it was loaded first time.
Please, help me figure out, what the problem is!
Thank you in advance!
After lots of hours debugging this, it seems, i found the problem.
I had a Page with DataContext set to MainViewModel, and this Page was a child of MainWindow, which had also DataContext property set to MainViewModel.
I didn't realize, that both Page and MainWindow created a MainViewModel instance of their own.
So i had two instances and while one was working and updating as it should, another was just confusing me.
Stupid mistake, i know.
Thanks anyway for trying to help.
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.
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; }
}
}