How to bind listview to custom item collection - c#

How do I get my custom item collection to show up in my list view using WPF data bindings?
I have a tried to make a ViewModel and a custom collection that the ViewModel manipulates, in an attempt to get this collection to show up in a listview. I have a view model and a custom collection and a custom item class:
public class TranslationViewModel
{
public TranslationViewModel() { this.translatedItems = new TransListboxCollection(); }
public TransListboxCollection translatedItems { get; private set; }
public void addTranslatedItem(TransListboxItem message)
{
translatedItems.Add(message);
}
}
public class TransListboxCollection : BindingList<TransListboxItem>
{
public TransListboxCollection()
{
//initialize
}
}
public class TransListboxItem : INotifyPropertyChanged
{
private String _rawString;
private String _tString;
public String rawString
{
get { return _rawString; }
set { _rawString = value; NotifyPropertyChanged("rawString"); }
}
public String tString
{
get { return _tString; }
set { _tString = value; NotifyPropertyChanged("tString"); }
}
public TransListboxItem(String value)
{
this.tString = value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return this.tString;
}
}
I have a WPF element hosted in a windows form
public partial class wGlobal : UserControl
{
public TranslationViewModel tvm { get; set; }
public wGlobal()
{
InitializeComponent();
this.DataContext = tvm;
}
}
The XAML code for such
<UserControl
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:MHF_Transcoder_3" x:Class="MHF_Transcoder_3.wGlobal"
mc:Ignorable="d"
d:DesignWidth="1000" d:DesignHeight="150">
<Grid Width="1000" Height="150">
<ListView x:Name="listView1" ItemsSource="{Binding tvm}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView>
</Grid>
and I have that element hosted in a windows form control
public partial class frmGlobal : Form
{
wGlobal xamlForm;
TranslationViewModel tvm;
public frmGlobal()
{
InitializeComponent();
tvm = new TranslationViewModel();
xamlForm = (wGlobal)elementHost1.Child;
xamlForm.tvm = tvm;
}
delegate void addMessageCallback(TransListboxItem message);
public void addMessage(TransListboxItem message) {
tvm.addTranslatedItem(message);
}
}
When I get the program up and launch everything, all my list view says is "System.Windows.DataTemplate". I've never really worked with WPF or data bindings before. I'm open to any and all advice and suggestions. Please help me get this setup and properly working.

Wrap Datatemplate with ItemTemplate
<ListView x:Name="listView1" ItemsSource="{Binding translatedItems}" HorizontalAlignment="Left" Width="1000" Height="150" VerticalAlignment="Top" Background="Black" Foreground="White" RenderTransformOrigin="0.5,0.5">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding tString}" ToolTipService.ToolTip="{Binding rawString}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Also as the tvm is the datacontext, you must bind to the collection "translatedItems"

Related

WPF ItemsControl can't bind Command

I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
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:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.

Show List in xaml using ViewModel

I have a UserControl called "UserControllerIo" and this is what it has:
public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
Messages = new ObservableCollection<string>();
InitializeComponent();
IoComponentViewModel.Instance = new IoComponentViewModel();
Label1.DataContext = IoComponentViewModel.Instance;
Messages.Add(Label1.Text);
}
I consume this in my xml like so:
<Grid>
<Label>
<TextBlock x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding Path=XState, Mode=OneWay}">
</TextBlock>
</Label>
<ListView
x:Name="ListView"
ItemsSource="{Binding Messages}" />
</Grid>
I have a view model for this control:
class IoComponentViewModel : INotifyPropertyChanged
{
public static IoComponentViewModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private string _xState;
public string XState
{
get { return _xState; }
set
{
_xState = value;
OnPropertyChanged($"XState");
}
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And I invoke to populate the list on another class like so:
case x:
IoComponentViewModel.Instance.XState = msg;
break;
My problem is, it is not showing in my Listview although I can see it in my label. Can you please show me how. Thank you.
I don't know how much I understood your task from the provided code, but look at this implementation variant.
IoComponentViewModel:
public class IoComponentViewModel : INotifyPropertyChanged
{
public static IoComponentViewModel Instance { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private string _xState;
public string XState
{
get { return _xState; }
set
{
if (_xState == value)
return;
XStates.Add(_xState = value);
OnPropertyChanged($"XState");
}
}
public ObservableCollection<string> XStates { get; } = new ObservableCollection<string>();
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Grid x:Name="PART_Grid">
<Grid.DataContext>
<local:IoComponentViewModel/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!--<Label>-->
<TextBlock x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding XState, Mode=OneWay}">
</TextBlock>
<!--</Label>-->
<ListView Grid.Row="1"
x:Name="ListView"
ItemsSource="{Binding XStates}" />
</Grid>
Code Behind:
//public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
//Messages = new ObservableCollection<string>();
InitializeComponent();
// IoComponentViewModel.Instance = new IoComponentViewModel();
//Label1.DataContext = IoComponentViewModel.Instance;
IoComponentViewModel.Instance = (IoComponentViewModel)PART_Grid.DataContext;
//Messages.Add(Label1.Text);
}
I misread the question initially. There are two problems. Your list is not binding to the view model, so you need an element reference.
<UserControl x:Class="StackOverflow.UserControllerIo"
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:StackOverflow"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="MyUserControl"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label>
<TextBlock Foreground="Black" x:Name="Label1" TextWrapping="WrapWithOverflow"
Text="{Binding Path=XState, Mode=OneWay}">
</TextBlock>
</Label>
<ListView Grid.Row="1"
x:Name="ListView"
ItemsSource="{Binding Messages, ElementName=MyUserControl}" >
</ListView>
</Grid>
Secondly, at the point where you add Label1.Text to your data binding is not ready. So you will need to wait for binding before you read the text, for example in load event like this:
public partial class UserControllerIo : UserControl
{
public ObservableCollection<string> Messages { get; set; }
public UserControllerIo()
{
Messages = new ObservableCollection<string>();
InitializeComponent();
IoComponentViewModel.Instance = new IoComponentViewModel();
Label1.DataContext = IoComponentViewModel.Instance;
IoComponentViewModel.Instance.XState = "Something";
Loaded += UserControllerIo_Loaded;
}
private void UserControllerIo_Loaded(object sender, RoutedEventArgs e)
{
Messages.Add(Label1.Text);
}
}
EDIT:
my first tests mislead me, by testing with an int property for adding values to the List on runtime.
ObservableCollection updates anyway!
The problem is how you declared the Messages Property. If you have a Property on a Control it needs to be a dependency Property to notify the UI.
replace
public ObservableCollection<string> Messages { get; set; }
with
public ObservableCollection<string> Messages
{
get { return (ObservableCollection<string>)GetValue(MessagesProperty); }
set { SetValue(MessagesProperty, value); }
}
public static readonly DependencyProperty MessagesProperty =
DependencyProperty.Register("Messages", typeof(ObservableCollection<string>), typeof(UserControllerIo), new PropertyMetadata(null));
and you should be fine.
OR
you could imlpement INotifyPropertyChanged on your UserControl class.
And don't forget to maintain #Clemens' comment about binding!!!
ItemsSource="{Binding Messages, RelativeSource={RelativeSource AncestorType=UserControl}}

How to set dependency property of ItemsControl.ItemTemplate and handle its events

I have a main control (MainWindow.xaml) and an user control (ItemView.xaml). MainWindow contains an ItemsControl for all the ItemView-s and a simple button to add an item. All logic is (should be?) inside two corresponding viewmodels (MainWindowViewModel and ItemViewModel). Below is my code (made it as short as possible), but I have two problems with it:
When a new item is added it is correctly displayed but the exception is raised (Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.ItemView' and 'WpfApplication1.ItemViewModel'.).
The OnDelete event handler in MainWindowViewModel is never raised? Edit: actually the ViewModel property inside BtnDeleteClick is null so yeah... of course.
Btw - I use Fody PropertyChanged.
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Width="100" Height="35" Content="Add" HorizontalAlignment="Left" Margin="10" Click="BtnAddClick"></Button>
<Border Grid.Row="1" MinHeight="50">
<ItemsControl ItemsSource="{Binding ViewModel.Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs:
[ImplementPropertyChanged]
public partial class MainWindow
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
}
private void BtnAddClick(object sender, RoutedEventArgs e)
{
ViewModel.Add();
}
}
MainWindowViewModel.cs:
[ImplementPropertyChanged]
public class MainWindowViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public void Add()
{
var item = new ItemViewModel();
item.OnDelete += (sender, args) =>
{
Debug.WriteLine("-- WAITING FOR THIS TO HAPPEN --");
Items.Remove(item);
};
Items.Add(item);
}
}
ItemViewModel.xaml:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button>
</Grid>
</UserControl>
ItemView.xaml.cs:
[ImplementPropertyChanged]
public partial class ItemView
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register
(
"ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null)
);
public ItemViewModel ViewModel
{
get { return (ItemViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public ItemView()
{
InitializeComponent();
}
private void BtnDeleteClick(object sender, RoutedEventArgs e)
{
ViewModel.Delete();
}
}
And ItemViewModel.cs:
[ImplementPropertyChanged]
public class ItemViewModel
{
public event EventHandler OnDelete;
public void Delete()
{
var handler = OnDelete;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
You should not set
DataContext="{Binding RelativeSource={RelativeSource Self}}"
in the XAML of your ItemView. It effectively breaks the ViewModel="{Binding .}" binding in MainWindow.xaml, because the DataContext is no longer an ItemsViewModel, but an ItemsView.
As a rule, you should never explicitly set the DataContext of a UserControl, because all "external" bindings would then require an explicit Source or RelativeSource value.
That said, you're doing all this way too complicated. Instead of having a button click handler in your ItemsView, you could simply have a view model with a delete command, and bind the Button's Command property to this command.
It may look like this:
public class ItemViewModel
{
public string Name { get; set; }
public ICommand Delete { get; set; }
}
public class MainViewModel
{
public MainViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public void AddItem(string name)
{
Items.Add(new ItemViewModel
{
Name = name,
Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))
});
}
}
and would be used like this:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Delete"
Command="{Binding Delete}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>

Data Template not shown

A listbox data template doesn't show and I cannot figure out why.
If I don't use a DataTemplate and copy the contents into the control section itself, it's fine.
I don't do very much binding in XAML, I usually do it all in code. What did I do wrong?
XAML
<UserControl x:Class="Cis.CustomControls.CisArrivalsPanel"
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"
mc:Ignorable="d" Height="296" Width="876">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate">
<ListBoxItem>
<StackPanel>
<TextBlock Background="Blue" Text="{Binding Path=StationName}" />
<TextBlock Background="Brown" Text="{Binding Path=ArrivalPlatform}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Width="487" Margin="0,66,0,33" ItemTemplate="{StaticResource DataTemplate}">
</ListBox>
</StackPanel>
</Grid>
</UserControl>
CS
public partial class CisArrivalsPanel : UserControl
{
public CisArrivalsPanel()
{
InitializeComponent();
this.DataContext = new ArrivalRowItem();
}
}
Model
public class ArrivalRowItem : INotifyPropertyChanged
{
public ArrivalRowItem()
{
this.StationName = "Lincoln";
this.ArrivalPlatform = "1";
}
private string _stationName;
public string StationName
{
get
{
return _stationName;
}
set
{
_stationName = value;
NotifyPropertyChanged("StationName");
}
}
private string _arrivalPlatform;
public string ArrivalPlatform
{
get
{
return _arrivalPlatform;
}
set
{
_arrivalPlatform = value;
NotifyPropertyChanged("ArrivalPlatform");
}
}
private DateTime _arrivalDateTime;
public DateTime ArrivalDateTime
{
get
{
return _arrivalDateTime;
}
set
{
_arrivalDateTime = value;
NotifyPropertyChanged("ArrivalDateTime");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You have everything set up, but you don't actually have any data.
ListBox, like other ItemsControls acts against a collection of data, and generates an instance of the template for each item it finds.
Given that you haven't set ItemsSource or populated any collection I can see, you need to create a collection (probably an ObservableCollection) and set the ItemsSource to it via binding. Then add some items to it, and the ListBox will display them!

setting the DataContex correctly

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.

Categories

Resources