EDITED:
I've created three ListBoxes:
1st ListBox: listBox, which shows a list of U.S. States
2nd ListBox: listBox_Mirror, which contains a CheckBox to show the selected items of "listBox"
3rd ListBox: (No Name), which is supposed to show the checked items of "listBox_Mirror"
1st ListBox -> 2nd ListBox works fine. However, 2nd ListBox -> 3rd ListBox doesn't work, as you can see in the picture below:
Procedure:
Select all four items in the 1st ListBox.
Check the first two items (California and Illioni) in the 2nd ListBox's CheckBox.
See if California and Illioni are shown in the 3rd ListBox.
(In my case, nothing is shown.)
Here are my codes:
StateList.cs
using System.Collections.ObjectModel;
namespace CheckedListBox
{
public class StateList
{
public ObservableCollection<string> Data { get; }
public StateList()
{
Data = new ObservableCollection<string>();
Data.Add("California");
Data.Add("Illinoi");
Data.Add("Michigan");
Data.Add("New York");
}
}
}
MainWindow.xaml
<Window x:Class="CheckedListBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CheckedListBox"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="400">
<Grid>
<ListBox ItemsSource="{Binding Path=Data}" x:Name="listBox" Margin="100,50,100,300" SelectionMode="Multiple"/>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=listBox}" x:Name="listBox_Mirror" Margin="100,200,100,150">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<CheckBox Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ListBox ItemsSource="{Binding SelectedItems, ElementName=listBox_Mirror}" Margin="100,350,100,25"/>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace CheckedListBox
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new StateList();
}
}
}
... I've changed the Binding property of the 3rd ListBox based on the IntelliSense's suggestion, but it doesn't work, too.
Please tell me how to fix this. Thank you.
Two things, there is no binding to the IsChecked and hence nothing ever is set. Also your data is just reference of strings; you should change it to a class with at least two properties, one a Boolean and the other the string you have now. Then bind appropriately.
Here is how to do it. I have a model which is defined in code behind but you can get the idea on its structure.
<Page ...
xmlns:model="clr-namespace:WPFStack.Model"/>
...
<Page.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
Now with my Listbox
<ListBox ItemsSource="{StaticResource Orders}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding CustomerName}"
IsChecked="{Binding InProgress, Mode=TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Running it shows this:
Model
namespace WPFStack.Model
{
/// <summary>Class Orders which is a placeholder for Xaml example data.</summary>
public class Orders : List<Order> { }
public class Order
{
public string CustomerName { get; set; }
public int OrderId { get; set; }
public bool InProgress { get; set; }
}
}
Mirror
Ok, now I will name the controls lbOriginal and lbSelected to be accessible in the code behind. The new control lbSelected will mirror as such without directly connecting to the lbOriginal control or the data:
<ListBox x:Name="lbShowSelected">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then I would subscribe to events such as Loaded, Checked and UnChecked on the original.
<ListBox x:Name="lbOriginal"
ItemsSource="{StaticResource Orders}"
Loaded="ProcessChange">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding CustomerName}"
IsChecked="{Binding InProgress, Mode=TwoWay}"
Checked="ProcessChange"
Unchecked="ProcessChange"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So then at each step ProcessChange method properly updates the mirror (selected as I call it):
private void ProcessChange(object sender, RoutedEventArgs e)
{
if (lbOriginal.ItemsSource is Orders asOrders)
{
lbShowSelected.ItemsSource = null; // Inform control of reset
lbShowSelected.ItemsSource = asOrders.Where(ord => ord.InProgress)
.Select(ord => ord.CustomerName)
.ToList();
}
}
Then it is in sync and mirroring
Related
I need to pass values from view to view model still preserving mvvm. Let me explain the issue.
<UserControl x:Class="SelectorView" ...>
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Command="{Binding DataContext.OnCheckedCommand,RelativeSource={RelativeSource Mode=Self}}" Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
In above code, When user checks/unchecks the checkbox, i need to send value(content) of checkbox and also value of vLabel from view to view model. Both are within nested Data template. Earlier I used visualtreehelper to retrieve the same but I need to do this more in mvvm format. Hence, want to know if its doable using Command binding and how ? In check box, i binded to OnCheckedCommand but that doesn't seem to invoke OnCheckedExecuted() on check/uncheck.
So, how do i get the content of checkbox and vLabel on check/uncheck?
public class MainViewModel
{
public DelegateCommand<object> OnCheckedCommand { get; private set; }
public MainViewModel()
{
OnCheckedCommand = new DelegateCommand<object>(OnCheckedExecuted, CanCheck);
}
private void OnCheckedExecuted(object parameter)
{
}
private bool CanCheck(object parameter)
{
return true;
}
}
Any help is appreciated!
Inside my class MainWindow I have:
public ObservableCollection<ViewModel> VMs ..
The MainWindow is constructed in the XAML (it creates an empty VMs in the class constructor too):
<Window.Resources>
<c:MainViewModel x:Key="ViewModelsSource"/>
</Window.Resources>
When I click on a button, I add ViewModel objects to the ObservableCollection VMs and the content of the ObservableCollection is shown in a ListBox:
<StackPanel DataContext="{Binding Source={StaticResource ViewModelsSource}}">
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding VMs}"
Background="Transparent"
HorizontalContentAlignment="Stretch"
> ...
The code for the Command Add is:
void AddListExecute()
{
VMs.Add(new ViewModel());
}
The constructor for ViewModel is:
public class ViewModel : MainViewModel
{
//Private Members
private ObservableCollection<FeeViewModel> _fees;
//Properties
public ObservableCollection<FeeViewModel> FVMs
{
get
{
return _fees;
}
set
{
_fees = value;
}
}
//Constructor
public ViewModel()
{
this._fees = new ObservableCollection<FeeViewModel>();
}
...
This part is working fine. Each ViewModel object contains another ObservableCollection:
public ObservableCollection<FeeViewModel> FVMs ..
I have a tabcontrol in the XAML that uses this ObservableCollection to do stuff:
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
...
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
...
EnabledTabs is a style that uses a property in FeeViewModel:
<Style TargetType="{x:Type TabControl}" x:Key="EnabledTabs">
<Setter Property="IsEnabled" Value="{Binding GotFees}"/>
</Style>
Now I have a binding error, FVMs is null and nothing is shown in the window. If I revert to a previous version without an ObservableCollection of ViewModel objects and I set the DataContext of the TabControl to that single ViewModel everything works fine.
How to set the DataContext of the TabControl to the dynamically created ViewModel objects?
Is it possible to do something like VMs/FVMs in the binding?
Thanks
Solved by adding DataContext to TabControl:
<TabControl
DataContext="{Binding VMs, Source={StaticResource ViewModelsSource}}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">
I'm trying to display a list of records by unit in separate columns. Everything is working except the binding to filter by unit.
I may be going about this completely the wrong way, but here's what I've got.
public class Record : INotifyPropertyChanged
{
public string Name { get; set; }
public string Unit { get; set; }
}
public class UnitList
{
public ObservableCollection<Record> Items { get; set; }
}
I'm using this markup extension code to do the filtering. In WPF can you filter a CollectionViewSource without code behind?
I can set the PropertyFilter Value statically and it filters fine, but all columns give me the same values. tbUnitName displays the correct value for Unit, but I can't get that same value passed in to the PropertyFilter Value I've tried the ProxyElement/FrameworkElement binding trick and as many different combinations of binding to parent elements as I can think of but I still get 'null' in the filter code.
In the window codebehind I've got:
List<string> names = (from s in this.SelectedSheets.Items
where !string.IsNullOrWhiteSpace(s.Unit)
select s.Unit).Distinct().ToList();
UnitNames = new ObservableCollection<string>(names);
lbUnits.ItemsSource = UnitNames;
Here's the XAML:
<ListBox Name="lbUnits" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="spRoster" Orientation="Horizontal" CanHorizontallyScroll="True" CanVerticallyScroll="False" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBox x:Name="tbUnitName" Text="{Binding Path=.}" />
<ListBox ItemsSource="{Binding }" ItemTemplate="{StaticResource listItemTemplate}" Margin="0,20,0,0" Name="lb1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="150" ScrollViewer.CanContentScroll="False" >
<ListBox.DataContext>
<CollectionViewSource Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=Items}" >
<!--Filter="FilterOutB"-->
<CollectionViewSource.Filter>
<local:Filter>
<local:PropertyFilter PropertyName="Unit" Value="{Binding Path=.}" />
</local:Filter>
</CollectionViewSource.Filter>
</CollectionViewSource>
</ListBox.DataContext>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel MaxWidth="{Binding ActualWidth, ElementName=lb1}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I think I've solved it. I added
<Grid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Grid.Resources>
and changed the filter to
<local:PropertyFilter PropertyName="Unit" Value="{Binding Source={StaticResource proxy}}" />
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.
I have a listbox inside a listbox, and both are binded to an observable collection. I have overloaded the SelectionChanged event for both. For the nested listbox, I have a few labels in its data template. I want to be able to get the content of those labels. It's just difficult because I cannot refer to any of them in the code behind, even with the x:name property defined. Anyone have any ideas?
<ListBox Grid.Row="5" x:Name="lb1" ItemsSource="{Binding}" DataContext="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionChanged="lb1_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtEnclosure" Content="{Binding Path=EnclosureID}"/>
<......other labels bound to other properties...>
<ListBox x:Name="lbserver" ItemsSource="{Binding Path=Slist}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Label x:Name="txtSlot" Content="{Binding Path=Slot}" />
<Label x:Name="txtServer" Content="{Binding Path=HostnameID}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The parent listbox binds to an observable collection called Elist (an observable collection of Enclosures, a class I defined)
this.DataContext = Settings.Elist;
And the child listbox binds to an observable collection inside of the Enclosure class.
public class Enclosure
{
public ObservableCollection<Server> Slist { get; set; }
...contains other variables as well....
}
In the application, it lists enclosures, and each enclosure has a list of servers. The user can select an Enclosure, and I can get the Enclosure from Elist based on the SelectedIndex (I use ElementAt(SelectedIndex)). Things just get much more tricky when I try to get one of the Servers from the nested listbox. I want to be able to select one of the servers in the list and get the Server from the observable collection Slist. The problem is that when the user selects the server directly, I don't know which Enclosure from Elist the server is from, aaand I can't get the SelectedIndex because I can't refer to anything from the nested listbox in the code behind >.< A very frustrating problem indeed...does anyone have any ideas?
If I can get at the items in the nested listbox in code that would be helpful as well.
Below sample shows how to get selected parent and child when user selects a child, see OnSelectedModelChanged method.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding .}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<ListBox ItemsSource="{Binding Path=Models}" SelectionChanged="OnSelectedModelChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Make> cars = new List<Make>();
cars.Add(new Make("Ford") { Models = new List<Model>() { new Model("F150"), new Model("Taurus"), new Model("Explorer") } });
cars.Add(new Make("Honda") { Models = new List<Model>() { new Model("Accord"), new Model("Pilot"), new Model("Element") } });
DataContext = cars;
}
private void OnSelectedModelChanged(object sender, SelectionChangedEventArgs e)
{
Selector modelSelector = sender as Selector;
Model selectedModel = modelSelector.SelectedItem as Model;
Make selectedMake = modelSelector.DataContext as Make;
}
}
public class Make
{
public Make(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<Model> Models { get; set; }
}
public class Model
{
public Model(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}