You know when you stare at something long enough it doesn't really make sense any more... I am trying to bind the background property of a listview item to whether it is part of a collection in the viewmodel. Here is a simplified version of what I'm working with:
<Grid>
<ListView x:Name="AirportListView"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="AirportListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Grid Padding="16">
<TextBlock Text="{x:Bind}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
and:
public sealed partial class MainPage : Page
{
public ObservableCollection<string> MyAirports { get; set; } = new ObservableCollection<string>();
public MainPage()
{
this.InitializeComponent();
AirportListView.ItemsSource = new List<string>()
{
"EDDB",
"LGIR",
"EHAM",
"LFPO",
"EGKK"
};
}
private void AirportListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is string clickedAirport)
{
if (MyAirports.Contains(clickedAirport))
MyAirports.Remove(clickedAirport);
else
MyAirports.Add(clickedAirport);
}
}
}
Ideally what I would like to achieve is to bind the background of the grid in the datatemplate so that it is a different colour when an item is part of MyAirports. I just haven't been able to figure out how to do this using x:Bind or Binding. I can think of a few more long winded ways to achieve the same thing but wondered if there is perhaps a more elegant solution with data binding.
Any thoughts would be much appreciated!
Will
You need to make the collection that goes to ItemsSource a richer class than just a string. It should contain the property IsMyAirport, then it can be bound to each ListViewItem as well to update its background.
<Page ...
xmlns:local="using:UwpQuestions.Views"
xmlns:common="using:UwpQuestions.Common">
<Page.Resources>
<common:MyBgConverter x:Key="myBgConverter"/>
</Page.Resources>
<Grid>
<ListView x:Name="AirportList"
SelectionMode="None"
IsItemClickEnabled="True"
HorizontalContentAlignment="Stretch"
ItemsSource="{x:Bind Airports}"
ItemClick="AirportListView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:AirportItem">
<Grid Padding="16" Width="150" Background="{x:Bind IsMyAirport, Mode=OneWay, Converter={StaticResource myBgConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
With the following code behind:
public sealed partial class AirportListView : Page
{
public ObservableCollection<string> MyAirports { get; set; } = new ObservableCollection<string>();
public ObservableCollection<AirportItem> Airports { get; set; }
public AirportListView()
{
this.InitializeComponent();
Airports = new ObservableCollection<AirportItem>
{
new AirportItem {Name = "EDDB"},
new AirportItem {Name = "LGIR"},
new AirportItem {Name = "LFPO"},
new AirportItem {Name = "EGKK"}
};
}
private void AirportListView_ItemClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is AirportItem clickedAirport)
{
if (MyAirports.Contains(clickedAirport.Name))
{
MyAirports.Remove(clickedAirport.Name);
clickedAirport.IsMyAirport = false;
}
else
{
MyAirports.Add(clickedAirport.Name);
clickedAirport.IsMyAirport = true;
}
}
}
}
public class AirportItem : BindableBase
{
private string _name;
public string Name
{
get { return _name; }
set { SetProperty<string>(ref _name, value); }
}
private bool _isMyAirport = false;
public bool IsMyAirport
{
get { return _isMyAirport; }
set { SetProperty<bool>(ref _isMyAirport, value); }
}
}
AirportItem uses BindableBase to notify the ListView of when properties in the class change. It implements INotifyPropertyChanged. If you're not familiar with it, you should read up on XAML databinding.
And, it also uses the MyBgConverter (that Laith defined) to change the bool value into a different background color.
Finally, with the IsMyAirport property on the class, you may not need a separate MyAirports string list. I didn't remove it because I wasn't sure if you were using it for something else. But your logic in AirportListView_ItemClick can be changed to just use the IsMyAirport bool rather than the MyAirports list.
Create a converter that converts bool to a SolidColorBrush and use that in the binding.
<ListView Background="{x:Bind IsPartOfMyAirports, Converter={StaticResource MyBgConverter}}">
...
</ListView>
Your view model would be responsible for notifying the change for IsPartOfMyAirports.
Your converter would look something like this:
public class MyBgConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var b = (bool)value;
var color = b ? Colors.Yellow : Colors.Green;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Related
I'm working on an UWP application. Which has a GridView with following structure:
<Page.Resources>
<local:boolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Page.Resources>
<GridView ItemsSource="{x:Bind ExampleItems, Mode=OneWay}" x:Name="mDataGridView" ItemClick="mDataGridView_ItemClick" IsItemClickEnabled="True">
<GridView.ItemTemplate>
<DataTemplate x:Name="DataTemplate" x:DataType="local:ItemTemplate">
<StackPanel Height="100" Width="100" Background="OrangeRed" x:Name="rootPanel">
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind Title,Mode=OneWay}"/>
<TextBlock Text="{x:Bind Subtitle,Mode=OneWay}" />
<TextBlock Text="{x:Bind Description,Mode=OneWay}" />
<ProgressBar Visibility="{x:Bind ShowProgress, Converter={StaticResource BoolToVisibilityConverter},Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
And a quite simple corresponding item data class:
public class ItemTemplate
{
public string Title { get; set; }
public string Subtitle { get; set; }
public string Description { get; set; }
public bool ShowProgress { get; set; }
}
A converter to convert the "ShowProgress" property into Visibility:
public class boolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool show = (bool)value;
return show ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
Visibility visibility = (Visibility)value;
return visibility.Equals(Visibility.Visible);
}
}
The code works fine to display the GridView when the application starts. But if I try to change the the progress bar visibility by changing corresponding "ShowProgress" property when the application is running, the view won't update.
ExampleItems[15].ShowProgress = true;
ExampleItems[15].Title = "New Title 15";
Any one got any idea for how to change the visibility with x:bind mechanism? Any suggestion would be appreciated. Thank you.
Alex
You need to implement the INotifyPropertyChanged interface and fire the PropertyChanged event whenever property values change.
for E.g :
public class ItemTemplate :INotifyPropertyChanged
{
private bool _showProgress;
public bool ShowProgress
{
get { return _showProgress; }
set
{
_showProgress = value;
RaisePropertyChanged("ShowProgress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
For the complete code listing, see the XAML data binding sample.
I have the below problem: I have two different user controls inside a parent user control. These are trainList, which holds a list of train objects and trainView, which is an user control that shows details of the selected train in the list.
My wish is to share a variable of trainList with trainView.
What I have now is:
Parent user control:
<UserControl>
<UserControl>
<customControls:trainList x:Name="trainList"></customControls:trainList>
</UserControl>
<UserControl>
<customControls:trainView x:Name="trainView"></customControls:trainView>
</UserControl>
<TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>
TrainList class:
public partial class TrainList : UserControl
{
public TrainList()
{
this.InitializeComponent();
this.DataContext = this;
}
public Train SelectedTrain { get; set; }
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.Print(this.SelectedTrain.Id);
}
}
Note: The Train class implements INotifyPropertyChanged.
If I got this to work, I'd apply the binding to the trainView user control (not sure if this would work) instead to the text block.
<UserControl>
<customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
</UserControl>
And then, I would access that variable someway from the code-behind of trainView.
(And after this, I would like to share a different variable from trainView with its parent user control, but maybe that's another question).
My current question is: could this be done this way or would I need to follow another strategy?
Take this simple view model, with a base class that implements the INotifyPropertyChanged interface, and a Train, TrainViewModel and MainViewModel class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(
ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
}
}
}
public class Train : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { SetValue(ref name, value); }
}
private string details;
public string Details
{
get { return details; }
set { SetValue(ref details, value); }
}
// more properties
}
public class TrainViewModel : ViewModelBase
{
public ObservableCollection<Train> Trains { get; }
= new ObservableCollection<Train>();
private Train selectedTrain;
public Train SelectedTrain
{
get { return selectedTrain; }
set { SetValue(ref selectedTrain, value); }
}
}
public class MainViewModel
{
public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}
which may be initialized in the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 1",
Details = "Details of Train 1"
});
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 2",
Details = "Details of Train 2"
});
}
The TrainDetails controls would look like this, of course with more elements for more properties of the Train class:
<UserControl ...>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Details}"/>
</StackPanel>
</UserControl>
and the parent UserControl like this, where I directly use a ListBox instead of a TrainList control:
<UserControl ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Trains}"
SelectedItem="{Binding SelectedTrain}"
DisplayMemberPath="Name"/>
<local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
</Grid>
</UserControl>
It would be instantiated in the MainWindow like this:
<Grid>
<local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>
Note that in this simple example the elements in the UserControls' XAML bind directly to a view model instance that is passed via their DataContext. This means that the UserControl know the view model (or at least their properties). A more general approach is to declare dependency properties in the UserControl class, that are bound to view model properties. The UserControl would then be independent of any particular view model.
im building a UserControl MyUserControl that has his own ViewModel MyUserControlViewModel. MyUserControl contains 6 VehicleSelectionBlock (V1, ... V6). VehicleSelectionBlock is a UserControl i've made. it has 3 RadioButton: car, train, bus; all are of enum type Vehicle and of the same GroupName VehicleGroup.
my goal is to represent each of MyUserControl's VehicleSelectionBlocks in MyUserControlViewModel.
to make my self clear: in MyUserControlViewModel i want to be able to know&change what RadioButton is checked in every one of the 6 VehicleSelectionBlock. i think my main problem is not the converter but rather the DataContex - i'm not sure how to set it correctly for each of the controllers.
iv'e tried Binding (which is the obvious solution). i tried reading here, here , and here. unfortunately neither one helped my acheive my goal.
my code is below - im kinda new to wpf and data binding in generally. i've read almost every chapter in this tutorial but still lost sometimes.
please help me get through this and understand better the DataContex concept.
ty
MyUserContlor.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for MyUserContlor.xaml
/// </summary>
public partial class MyUserContlor : UserControl
{
public MyUserContlorViewModel ViewModel { get; set; }
public MyUserContlor()
{
ViewModel = new MyUserContlorViewModel();
InitializeComponent();
this.DataContext = ViewModel;
}
private void BtnImReady_OnClick(object sender, RoutedEventArgs e)
{
//this code is irrelevant to the question
throw NotImplementedException();
}
}
}
MyUserContlor.xaml:
<UserControl x:Class="Project01.MyUserContlor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:loc="clr-namespace:Project01"
mc:Ignorable="d"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<StackPanel>
<loc:VehicleSelectionBlock Name="V1"/>
<loc:VehicleSelectionBlock Name="V2"/>
<loc:VehicleSelectionBlock Name="V3"/>
<loc:VehicleSelectionBlock Name="V4"/>
<loc:VehicleSelectionBlock Name="V5"/>
<loc:VehicleSelectionBlock Name="V6"/>
<Button x:Name="BtnImReady" Click="BtnImReady_OnClick">Im Ready!</Button>
</StackPanel>
</Viewbox>
</UserControl>
MyUserContlorViewModel.cs:
namespace Project01
{
public class MyUserContlorViewModel : INotifyPropertyChanged
{
public MyUserContlorViewModel()
{
VehicleArr = new MyViewModel_Vehicle[6];
PropertyChanged+=MyUserControlViewModel_PropertyChanged;
}
public MyViewModel_Vehicle[] VehicleArr;
public event PropertyChangedEventHandler PropertyChanged;
public PropertyChangedEventHandler GetPropertyChangedEventHandler() { return PropertyChanged; }
private void MyUserControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//might be useful
throw NotImplementedException();
}
}
//this class should represent a VehicleSelectionBlock
public class MyViewModel_Vehicle
{
public Vehicle VehicleSelected {get; set;}
MyViewModel_Vehicle(){}
MyViewModel_Vehicle(Vehicle v){ VehicleSelected = v;}
}
}
VehicleSelectionBlock.xaml:
<UserControl x:Class="Project01.VehicleSelectionBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Project01"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Background="GhostWhite"
BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel >
<Label Content="{Binding Name}"
FontWeight="Bold" HorizontalContentAlignment="Center"></Label>
<RadioButton GroupName="VehicleGroup" >car</RadioButton>
<RadioButton GroupName="VehicleGroup">train</RadioButton>
<RadioButton GroupName="VehicleGroup" IsChecked="True">bus</RadioButton>
</StackPanel>
</Border>
</Grid>
</UserControl>
VehicleSelectionBlock.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for VehicleSelectionBlock.xaml
/// </summary>
public partial class VehicleSelectionBlock : UserControl
{
public VehicleSelectionBlock()
{
InitializeComponent();
}
public VehicleSelectionBlock(String name)
{
name = Name;
InitializeComponent();
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name", typeof (String), typeof (VehicleSelectionBlock), new PropertyMetadata(default(String)));
public String Name
{
get { return (String) GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
public enum Vehicle { Car, Train, Bus}
}
here is a quick solution. keep in mind that the code needs to change if you want to add more values to your Vehicle enum.
the MyUserControlViewModel.cs file
public class MyUserControlViewModel
{
public MyUserControlViewModel()
{
VehicleArr = new VehicleViewModel[6];
for (int i = 0; i < 6;i++ )
VehicleArr[i] = new VehicleViewModel();
}
public VehicleViewModel[] VehicleArr { get; set; }
}
this will expose your 6 items. They could be more. As a result they will be displayed in an ItemsControl, as you will see later.
public class VehicleViewModel:ViewModelBase
{
private bool isCar, isTrain, isBus;
public bool IsCar
{
get { return isCar; }
set
{
if (isCar == value) return;
isCar = value;
OnChanged("IsCar");
}
}
public bool IsTrain
{
get { return isTrain; }
set
{
if (isTrain == value) return;
isTrain = value;
OnChanged("IsTrain");
}
}
public bool IsBus
{
get { return isBus; }
set
{
if (isBus == value) return;
isBus = value;
OnChanged("IsBus");
}
}
}
instances of VehicleViewModel will contain your radio selection using 3 bool properties. this is the solution disadvantage. If you want more values you'll have to add more properties. you can see this inherits ViewModelBase. ViewModelBase just implements INPC so i'm not going to put it here. ViewModelBase also exposes the OnChange method that triggers the INPC event.
displaying the list can be done in your MyUserControl by using an ItemsControl like below.
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<loc:VehicleControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
each item is also a UserControl. The VehicleControl user control is just a StackPanel that displays the RadioButons. This can be seen below.
<StackPanel Orientation="Horizontal">
<RadioButton Content="Car" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsCar, Mode=TwoWay}"/>
<RadioButton Content="Train" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsTrain, Mode=TwoWay}"/>
<RadioButton Content="Bus" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsBus, Mode=TwoWay}"/>
</StackPanel>
please notice that each RadioButton is bound to one of the 3 properties in the VehicleViewModel instance.
Once you press your button you should have all the selections recorded. if you want you could have a function that returns an enum value by analysing the 3 bool properties if that is what you need.
the best solution will be to get rid of the radio buttons and replace them with combo boxes. in this way you can change the enum members and everything will continue to work without changing anything else. this might look as below.
public class VehicleViewModel:ViewModelBase
{
private Vehicle selOption;
private readonly Vehicle[] options;
public VehicleViewModel()
{
this.options = (Vehicle[])Enum.GetValues(typeof(Vehicle));
}
public Vehicle[] Options { get { return options; } }
public Vehicle SelectedOption
{
get { return selOption; }
set
{
if (selOption == value) return;
selOption = value;
OnChanged("SelectedOption");
}
}
}
and for the view:
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can do directly in the code-behind of your control (in the default constructor)
public VehicleSelectionBlock()
{
InitializeComponent();
this.DataContext = new MyUserContlorViewModel ();
}
You can also do that in XAML (http://msdn.microsoft.com/en-us/library/ms746695(v=vs.110).aspx) declaration, as you wish.
I need to create an UI which allows me to select entries from one list box and add it to another listbox at the run time. Now, the listbox1 may contain combo box and checkbox as the items.
For example, if I add a combo box labelled Quarter with values "Q1, Q2, Q3, Q4" as an item in listbox1 and select the entry Q1 in it, and click on the "Add" button, it should be added to listbox2. Vice versa should also be possible. This should be possible at the run time. How could I add combo box and checkbox as an item to the listbox? Also, please suggest if for the add-remove buttons, the code I've is correct.
private void MoveListBoxItems(ListBox source, ListBox destination)
{
ListBox.SelectedObjectCollection sourceItems = source.SelectedItems;
foreach (var item in sourceItems)
{
destination.Items.Add(item);
}
while (source.SelectedItems.Count > 0)
{
source.Items.Remove(source.SelectedItems[0]);
}
}
private void button1_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox1, listBox2);
}
private void button2_Click(object sender, EventArgs e)
{
MoveListBoxItems(listBox2, listBox1);
}
This is a WPF solution to your need. I am posting it because you told me it could be useful for you. It largely surpasses anything you can ever hope to achieve in winforms, which is a very limited and outdated technology.
This is how it looks in my screen:
I am using some simple ViewModels to represent the data:
ListItemViewModel (the "base" one):
public class ListItemViewModel: ViewModelBase
{
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
NotifyPropertyChange(() => DisplayName);
}
}
}
BoolListItemViewModel (for CheckBoxes):
public class BoolListItemViewModel: ListItemViewModel
{
private bool _value;
public bool Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged(() => Value);
}
}
}
SelectableListItemViewModel (for ComboBoxes):
public class SelectableListItemViewModel: ListItemViewModel
{
private ObservableCollection<ListItemViewModel> _itemsSource;
public ObservableCollection<ListItemViewModel> ItemsSource
{
get { return _itemsSource ?? (_itemsSource = new ObservableCollection<ListItemViewModel>()); }
}
private ListItemViewModel _selectedItem;
public ListItemViewModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NotifyPropertyChange(() => SelectedItem);
}
}
}
This is the "Main" ViewModel, which holds the 2 lists and the Commands (the Button actions)
public class ListBoxSampleViewModel: ViewModelBase
{
private ObservableCollection<ListItemViewModel> _leftItems;
public ObservableCollection<ListItemViewModel> LeftItems
{
get { return _leftItems ?? (_leftItems = new ObservableCollection<ListItemViewModel>()); }
}
private ObservableCollection<ListItemViewModel> _rightItems;
public ObservableCollection<ListItemViewModel> RightItems
{
get { return _rightItems ?? (_rightItems = new ObservableCollection<ListItemViewModel>()); }
}
private DelegateCommand<ListItemViewModel> _moveToRightCommand;
public DelegateCommand<ListItemViewModel> MoveToRightCommand
{
get { return _moveToRightCommand ?? (_moveToRightCommand = new DelegateCommand<ListItemViewModel>(MoveToRight)); }
}
private void MoveToRight(ListItemViewModel item)
{
if (item != null)
{
LeftItems.Remove(item);
RightItems.Add(item);
}
}
private DelegateCommand<ListItemViewModel> _moveToLeftCommand;
public DelegateCommand<ListItemViewModel> MoveToLeftCommand
{
get { return _moveToLeftCommand ?? (_moveToLeftCommand = new DelegateCommand<ListItemViewModel>(MoveToLeft)); }
}
private void MoveToLeft(ListItemViewModel item)
{
if (item != null)
{
RightItems.Remove(item);
LeftItems.Add(item);
}
}
}
This is the entire XAML for the Window:
<Window x:Class="WpfApplication4.Window14"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Window14" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ListItemViewModel}">
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BoolListItemViewModel}">
<CheckBox Content="{Binding DisplayName}" IsChecked="{Binding Value}" HorizontalAlignment="Left"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectableListItemViewModel}">
<ComboBox ItemsSource="{Binding ItemsSource}" SelectedItem="{Binding SelectedItem}"
HorizontalAlignment="Stretch" MinWidth="100"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding LeftItems}"
x:Name="LeftList"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<Button Content="Move to Right"
Command="{Binding MoveToRightCommand}"
CommandParameter="{Binding SelectedItem,ElementName=LeftList}"/>
<Button Content="Move to Left"
Command="{Binding MoveToLeftCommand}"
CommandParameter="{Binding SelectedItem,ElementName=RightList}"/>
</StackPanel>
<ListBox ItemsSource="{Binding RightItems}"
Grid.Column="2" x:Name="RightList"/>
</Grid>
</Window>
and finally, this is the Window Code-behind, which only initializes the ViewModel with some items:
public partial class Window14 : Window
{
public Window14()
{
InitializeComponent();
DataContext = new ListBoxSampleViewModel()
{
LeftItems =
{
new ListItemViewModel(){DisplayName = "Item1"},
new BoolListItemViewModel() {DisplayName = "Check Item 2", Value = true},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Combo Item 1"},
new BoolListItemViewModel() {DisplayName = "Check inside Combo"},
new SelectableListItemViewModel()
{
ItemsSource =
{
new ListItemViewModel() {DisplayName = "Wow, this is awesome"},
new BoolListItemViewModel() {DisplayName = "Another CheckBox"}
}
}
}
}
}
};
}
}
At first glance, this might seem like a LOT of code... but if you take 2 seconds to analyze it... Its just "simple, simple properties and INotifyPropertyChanged. That's how you program in WPF.
I'm talking about a completely different paradigm from what you might be used to in winforms, but it's really worth the effort of learning it. Notice that nowhere in my code I am interacting with UI elements. I just create the ViewModel structure and let the WPF Binding System to take care of generating the UI for me, using the provided DataTemplates.
I'm using the ViewModelBase from MVVM Light and the DelegateCommand from WPFTutorial.net. You can copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself (you will also need these 2 classes from the links above)
If you need to integrate this in an existing winforms application, you will need the ElementHost
I want to develop dialog for editing objects that make use of polymorphism. Currently I'm using this pattern:
MyObject.cs:
using System;
namespace WpfApplication3
{
public class MyObject
{
public string Title { get; set; }
public MySettings Settings { get; set; }
}
public abstract class MySettings
{
public abstract string GetSettingsString();
}
public class MyBoolSettings : MySettings
{
public bool BoolSetting { get; set; }
public override string GetSettingsString()
{
return "BoolSetting = " + BoolSetting;
}
}
public class MyStringSettings : MySettings
{
public string StringSetting { get; set; }
public override string GetSettingsString()
{
return "StringSetting = " + StringSetting;
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication3.EditMyObjectDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EditMyObjectDialog" Height="350" Width="350">
<StackPanel Margin="20">
<TextBlock Text="Title" />
<TextBox Name="txtTitle" />
<RadioButton Name="rdBoolSettings" Content="BoolSettings" IsChecked="True" Margin="0, 20, 0, 0" />
<CheckBox Name="chBool" Content="True" Margin="20, 0, 0, 20" />
<RadioButton Name="rdStringSettings" Content="StringSettings" />
<TextBox Name="txtString" Margin="20, 0, 0, 20"/>
<Button Content="OK" Click="OK_click" />
<Button Content="Cancel" Click="Cancel_click" Margin="0, 10" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfApplication3
{
public partial class EditMyObjectDialog : Window
{
public MyObject Result { get; set; }
public EditMyObjectDialog(MyObject objectToEdit)
{
InitializeComponent();
txtTitle.Text = objectToEdit.Title;
if (objectToEdit.Settings is MyBoolSettings)
{
rdBoolSettings.IsChecked = true;
chBool.IsChecked = (objectToEdit.Settings as MyBoolSettings).BoolSetting;
}
if (objectToEdit.Settings is MyStringSettings)
{
rdBoolSettings.IsChecked = true;
txtString.Text = (objectToEdit.Settings as MyStringSettings).StringSetting;
}
}
private void OK_click(object sender, RoutedEventArgs e)
{
Result = new MyObject() { Title = txtTitle.Text };
if (rdBoolSettings.IsChecked == true)
Result.Settings = new MyBoolSettings() { BoolSetting = chBool.IsChecked == true };
if (rdStringSettings.IsChecked == true)
Result.Settings = new MyStringSettings() { StringSetting = txtString.Text };
DialogResult = true;
}
private void Cancel_click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
ExternalCode:
var f = new EditMyObjectDialog(myObject);
if (f.ShowDialog() == true)
myObject = f.Result;
I belive there is much better design pattern that uses data binding etc. So basically I have two questions.
How to make data binding not to
modify object until user hits 'OK'?
How to correctly handle 'Settings'
property? What to do when user
switches setting's type?
What I believe you're looking for is a combination of DataBinding and DataTemplating. DataTemplating will allow you to define different visual elements for different business objects (in this case MyBooleanSettings and MyStringSettings. DataBinding will allow the visual elements to update and be updated my the data in the business objects.
Example (xaml):
<Window DataContext={Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBlock Text={Binding Title}" />
<ContentPresenter Content="{Binding Settings}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBox Text={Binding
</DataTemplate>
<DataTemplate DataType={x:Type local:MyBoolSettings}>
<CheckBox IsChecked="{Binding BoolSetting}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyStringSettings}>
<TextBox Text="{Binding StringSetting}" />
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding ObjectToEdit}" />
</Window>
Then in the code behind define:
public MyObject ObjectToEdit { get; set; }
Finally update your objects:
public class MySettings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(sting s)
{
if(PropertyChanged != null)
{
PropertyChanged(s);
}
}
}
public class BoolSettings : MySettings
{
bool _value;
bool BoolSetting
{
get { return _value; }
set
{
if(_value != value)
{
_value = value;
OnPropertyChanged("BoolSetting");
}
}
}
}
If however you really need to control when the view and object sync you should use the UpdateSourceTrigger property on the corresponding bindings.
If you want some additional reading I recommend: http://msdn.microsoft.com/en-us/library/ms752347.aspx
DataBinding is Simple . You can create an instance of MyObject and assign it to the DataContext property of the Form.
this.DataContext=MyObject;
And define binding for individual elements.
<TextBox Name="txtTitle" Text="{Binding Path=Title,Mode=TwoWay }" />
Setting mode as two way will affect the object as you make change in UI. One way will show the values.
How to make data binding not to modify object until user hits 'OK'?
Create a copy of the MyObject instance. In the Result property get method, return copy if user hit cancel (return unchanged copy) or if user hit OK, return the changed MyObject instance.
How to correctly handle 'Settings' property? What to do when user switches setting's type?
Whats the problem?