I have a MainWindow that contains A listbox, and a ContentControl, each time you select something from the Listbox, the ContentControl will display something else.
<ContentControl Content="{Binding ElementName=SomeList, Path=SelectedItem.Content}" />
<ListBox x:Name="SomeList" Margin="0 16 0 16" SelectedIndex="0" SelectedValue="{Binding X}"
ItemsSource="{Binding DemoItems}">
ViewModel:
private string _X;
public string X
{
get { return _X; }
set
{
_X = value;
NotifyOfPropertyChange("X");
}
}
Trying to display X will result in the same thing:
namespace.DemoItem
DemoItem.cs:
public class DemoItem : INotifyPropertyChanged
{
private object _icon;
private string _name;
private object _content;
private Thickness _marginRequirement;
public DemoItem(object icon, string name, object content, Thickness margin, IEnumerable<DocumentationLink> documentation)
{
_icon = icon;
_name = name;
Content = content;
_marginRequirement = margin;
Documentation = documentation;
}
}
So how is it possible to only get the name?
Data binding only works with public properties. So add a Name property
public class DemoItem : INotifyPropertyChanged
{
...
public string Name { get { return _name; } }
}
Assuming that yous intention is to selected a DemoItem by its Name, you should also set the ListBox's SelectedValuePath in conjunction with SelectedValue:
<ListBox ItemsSource="{Binding DemoItems}"
SelectedValuePath="Name"
SelectedValue="{Binding X}" />
Then simply display the selected item's Name like this:
<TextBlock Text="{Binding X}" />
Related
i have the following ItemsControl defined, i want to bind the Content of a Button to a object Property path, which will be provided as a string through a method. The Items are generic. How can i achieve this ? because i can't set DisplayMemberPath and bind it to the ItemTemplate. (i cant't set both DisplayMemberPath and ItemTemplate)
<ScrollViewer Grid.Row="2"
Grid.Column="1"
Margin="5 3"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Style="{DynamicResource MaterialDesignFlatButton}"
Margin="2"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock Text="{Binding}" />
</Button.Content>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
ViewModel:
public class SelectionViewModel<T> : DialogViewModelBase, IDialogViewModel
{
// Fields
private string _title;
private bool _showTitleSeparator;
private string _displayMemberPath;
private IEnumerable<T> _items;
// Properties
public string Title
{
get { return _title; }
set { _title = value; Notify(); }
}
public IEnumerable<T> Items
{
get { return _items; }
set { _items = value; Notify(); }
}
// Member Path to bind to
public string DisplayMemberPath
{
get { return _displayMemberPath; }
set { _displayMemberPath = value; Notify(); }
}
}
if i remove the ItemTemplate and simply bind the DisplayMemberPath of the ItemsControl to the Property in my ViewModel it works fine.
I was able to do this using a converter and reflection.
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Contains(DependencyProperty.UnsetValue))
{ return null; }
object source = values[0];
string path = (string)values[1];
Type sourceType = source.GetType();
System.Reflection.PropertyInfo propertyInfo = sourceType.GetProperty(path);
return propertyInfo.GetValue(source, null);
}
Binding has to be set with Path to the property name of the class whose list is bound to the ItemsSource.
if your class looks like:
Class MyItem{
String Prop1 { get; }
}
Then binding will be
<TextBlock Text="{Binding Path=Prop1}" />
<Stackpanel>
<TextBox x:Name="txtid" Width="90" Text={Binding Name} Height="25"/>
<TextBox x:Name="txtname" Width="90" Text={Binding Age} Height="25" Margin="0 10 0 10"/>
<Button Command={Binding AddCommand} Content="Add"/>
<ListView ItemsSource={Binding StudentList}/>
</Stackpanel>
ViewModel
public class StudentViewModel : INotifyPropertyChanged
{
public StudentViewModel()
{
_studentList = new ObservableCollection<StudentDetails>();
LoadCommand();
}
private ObservableCollection<StudentDetails> _studentList;
public ObservableCollection<StudentDetails> StudentList
{
get { return _studentList; }
set
{
_studentList = value;
OnPropertyChanged("StudentList");
}
}
public StudentDetails SelectedItems { get; set; }
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}
public ICommand AddCommand { get; set; }
public void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
StudentList.Add(new StudentDetails { Name = Name, Age = Age });
}}
Model
public class StudentDetails : INotifyPropertyChanged
{
private string _name;
private int _age;
public string Name
{
get { return _name;}
set { _name = value; OnPropertyChanged("Name")}
}
public string Age
{
get { return _age;}
set { _age = value; OnPropertyChanged("Age")}
}}
I have two textbox and a listview like above. how to do two way binding using MVVM?? which means the entered textbox value should add to listview and if i select the values in the listview then the selected value should bind to the same textbox so that i can update the values. how to do it??
I have tried to run your code. It has lots of errors. Anyways from your question I think you want to have a listview and when user selects a particular list item, the corresponding age and name is displayed in text boxes and if user want to update the data, you want to add it to the list. First create a list view with data template that binds to the class file properties.
Now also create a StudentDetails object and bind the SelectedItem of the list view to it.
When user selects a list item, you get SelectionChanged event. During this time update the property of 2 text boxes to display the selected list items data in them.
Now in the add button event handler, update the list data for corresponding selected item. Make sure you bind the listitemssource to an ObservableCollection
I would have different view models for an individual student and for the student list. Name and Age properties don't actually belong to the list.
I used MVVM Light syntax for the example:
StudentViewModel
public class StudentViewModel : ViewModelBase
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set { Set<string>(ref _name, value); }
}
public int Age
{
get { return _age; }
set { Set<int>(ref _age, value); }
}
}
StudentView.xaml
<UserControl x:Class="MasterDetailExample.Views.StudentView"
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:local="clr-namespace:MasterDetailExample.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock Text="Name: "/>
<TextBox Text="{Binding Name}" Width="150"/>
<TextBlock Text="Age: "/>
<TextBox Text="{Binding Age}" Width="20"/>
</WrapPanel>
</UserControl>
Now StudentsViewModel represents the student list:
public class StudentsViewModel : ViewModelBase
{
private ObservableCollection<StudentViewModel> _studentList;
private StudentViewModel _selectedStudent;
public StudentsViewModel()
{
StudentList = new ObservableCollection<StudentViewModel>();
StudentList.Add(new StudentViewModel { Name = "Joe", Age = 21 });
StudentList.Add(new StudentViewModel { Name = "Jane", Age = 19 });
}
public ObservableCollection<StudentViewModel> StudentList
{
get { return _studentList; }
private set { _studentList = value; }
}
public StudentViewModel SelectedStudent
{
get { return _selectedStudent; }
set { Set<StudentViewModel>(ref _selectedStudent, value); }
}
}
** List view, StudentsView**
<UserControl x:Class="MasterDetailExample.Views.StudentsView"
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:views="clr-namespace:MasterDetailExample.Views"
xmlns:vm="clr-namespace:MasterDetailExample.ViewModel"
d:DesignHeight="300"
d:DesignWidth="500"
mc:Ignorable="d">
<UserControl.Resources>
<vm:StudentsViewModel x:Key="StudentsVm" />
</UserControl.Resources>
<DockPanel DataContext="{StaticResource StudentsVm}">
<ListView DockPanel.Dock="Left" Width="100" ItemsSource="{Binding StudentList}" SelectedItem="{Binding SelectedStudent}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Separator />
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
</DockPanel>
</UserControl>
Directly setting the DataContext kind of smells,
<views:StudentView DockPanel.Dock="Right" DataContext="{Binding SelectedStudent}"/>
In more complicated example, you would either create a DependencyProperty for the SelectedStudent, or implement some messaging logic to communicate between different view models.
I have created a custom control derived from a ListBox and I am having issues getting the "SelectedItemsList" to bind to it's corresponding property in the view model.
The problem: The selected items in the list box do not make it into the property it is bound to in the view model. The list box allows multiple selections but none of these make into the List in the view model.
MultiSelectListBox:
public class MultiSelectListBox : ListBox
{
public MultiSelectListBox() { }
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register(
"SelectedItemsList",
typeof(IList),
typeof(MultiSelectListBox),
new PropertyMetadata(default(IList)));
public IList SelectedItemsList
{
get { return (IList) GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
}
declaration in MainWindow.xaml:
<local:MultiSelectListBox
DataContext="{StaticResource viewModel}"
DockPanel.Dock="Left"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}"
ItemsSource="{Binding SelectedOutputtapeList}"
SelectionMode="Multiple"
SelectedItemsList="{Binding SelectedOutputTapes, Mode=TwoWay}"
HorizontalAlignment="Right"
Background="DeepSkyBlue"
Foreground="MidnightBlue"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Height="100"
Width="70"
Margin="5"/>
View Model (simplified):
public class BTLogFrontEndViewModel : ViewModelBase
{
private List<string> selectedOutputTapes;
public BTLogFrontEndViewModel()
{
selectedOutputTapes = new List<string>();
}
public List<string> SelectedOutputTapes
{
get
{
return selectedOutputTapes;
}
set
{
selectedOutputTapes = value;
OnPropertyChanged("SelectedOutputTapes");
}
}
}
One way is to not use a custom ListBox and use objects in your list that extend INotifyPropertyChanged:
<ListBox
Width="70"
Height="100"
HorizontalAlignment="Right"
Margin="5"
Background="DeepSkyBlue"
DockPanel.Dock="Left"
Foreground="MidnightBlue"
ItemsSource="{Binding SelectedOutputtapeList}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
DisplayMemberPath="Description"
Visibility="{Binding IsThailandFinal, Converter={StaticResource BoolToVisConverter}, FallbackValue=Visible}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Assuming BTLogFrontEndViewModel is your DataContext:
public class BTLogFrontEndViewModel : ViewModelBase
{
private ObservableCollection<OutputTapeViewModel> m_SelectedOutputtapeList;
public ObservableCollection<OutputTapeViewModel> SelectedOutputtapeList
{
get
{
return m_SelectedOutputtapeList;
}
set
{
m_SelectedOutputtapeList = value;
NotifyPropertyChanged("SelectedOutputtapeList");
}
}
}
Where OutputTapeViewModel is declared like:
public class OutputTapeViewModel : ViewModelBase
{
private string m_Description;
public string Description
{
get
{
return m_Description;
}
set
{
m_Description = value;
NotifyPropertyChanged("Description");
}
}
private bool m_IsSelected;
public string IsSelected
{
get
{
return m_IsSelected;
}
set
{
m_IsSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
Important things to note is on the listbox I have added the DisplayMemberPath property for use of the description field in the OutputTapeViewModel class for what to show on the listbox. Also it has an item container style, which binds to the OutputTapeViewModel.IsSelected property when selected in the ListBox. This OutputTapeViewModel.IsSelected allows you to use the BTLogFrontEndViewModel.SelectedOutputtapeList property in such ways as:
var selectedItems = SelectedOutputtapeList.Where(item => item.IsSelected);
This only works in your case if you don't care about the overhead of doing the LINQ expression.
for the past couple of weeks I've been trying to get the EditableTextBlock (from codeproject) working on my TreeView.
The control has a property IsInEditMode which when set to true changes it to a TextBox.
The TreeView is virtualized and declared as follows:
<TreeView x:Name="treeEnvironment"
Margin="0,29,0,0" BorderThickness="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
</TreeView>
The TreeView uses the ItemsSource property to get it's data and the value of this is always a single instance of a class (lets call it A). This class contains a list of instances of another type (lets call it B). And this last class contains a list of instances of yet another class (lets call it `C). This is how it looks like in the code:
class A
{
public String Name;
public ObservableCollection<B> Items;
}
class B
{
public String Name;
public ObservableCollection<C> Items;
}
class C
{
public String Name;
public bool IsRenaming;
}
For each of these three classes there is an HierarchicalDataTemplate defined in MainWindow.Resources as follows:
<DataTemplate DataType="{x:Type C}">
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Click="C_Rename_Click" />
</ContextMenu>
</StackPanel.ContextMenu>
<v:EditableTextBlock Text="{Binding Path=Name}" IsInEditMode="{Binding Path=IsRenaming, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type B}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<Image Source="icons/icon_A.png" Width="16" Height="16" />
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
None of the DataTemplate have keys so that it is applied automatically.
The event that is triggered when the rename MenuItem of C's context menu is clicked, is defined as follows:
private void C_Rename_Click(object sender, RoutedEventArgs e)
{
C instance = (sender as FrameworkElement).DataContext as C;
if (instance != null) {
instance.IsRenaming = true;
} else {
MessageBox.Show("DEBUG: C_Rename_Click(" + sender.ToString() + ", " + e.ToString() + ") : instance == null");
}
}
The problem is that the EditableTextBlock does not turn into a TextBox when the IsRenaming property is set to true on the instance of C that was chosen to be renamed.
The EditableTextBlock works just fine when I place it as a normal control.
My guess is that it has to do with virtualization. Any help would be appreciated.
Thank you for your time, best regards,
100GPing100.
class A, B, C need to implement INotifyPropertyChanged for any changes made to them to be propagated to the UI. You can either implement it in each class individually or have a base class implement INPC and derive your classes from this base class.
Something like:
public class MyBaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class A : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<B> _items;
public ObservableCollection<B> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class B : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<C> _items;
public ObservableCollection<C> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class C : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private bool _isRenaming;
public bool IsRenaming {
get {
return _isRenaming;
}
set {
_isRenaming = value;
OnPropertyChanged("IsRenaming");
}
}
}
Now when you change IsRenaming in your code, you will see the update propagate to the UI and the TextBlock switch to a TextBox.
Side-note
Please have a look at MVVM. If you're not sure about it. Get to learn it slowly cos it helps UI development in WPF quite a bit.
Hi all i have problem in this code, please help me..
I have view
<StackPanel Orientation="Horizontal" Margin="3">
<Label Content="Audit Type" MinWidth="100"/>
<Label Content=":"/>
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding Items}" Margin="3" SelectionMode="Extended" MinWidth="180">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Name="check" Content="{Binding Value}" IsChecked="{Binding IsChecked, Mode=TwoWay}" Margin="3" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
and for View model
private List<AuditTypeExport> _items;
private List<string> _value;
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public List<AuditTypeExport> Items
{
get { return _items; }
}
public List<string> Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
And ViewModel Constractor
_items = _model.GetAuditType();
_value = _model.GetAuditType().Select(item => item.Name).ToList();
For your information
public class AuditTypeExport
{
private int _id;
private string _name;
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
The result : checkbox appeares, but the content doesn't and I don't have a clue why.
Question Number 2 : I want to get the value back, how can I do that?
Thank you
It is unclear how you are using your ViewModel. Is that bound to the form? Or each item in the ListBox?
It looks like your ListBox is bound to the Items collection of your VM, so the ItemTemplate will be used with a AuditTypeExport as the data context. You are binding to "Value" and "IsChecked" properties which do not exist on the AuditTypeExport class.
What you are trying to do here is bind a property of type List<String> Value to CheckBox's Content property which is of type Object.
To simplify, you are assigning a collection of strings to a string. Which is not a good thing. And that is why it does not work.
Try using ItemsControl to show Value property or use an IValueConverter to convert List<String> to comma separated string.