Combobox binding doesn't work in ItemsControl using MVVM - c#

I have a ComboBox in ItemsControl .I use WPF and MVVM, I have problem to figure out the binding to ComboBox, would someone give me a hand for this. XAML and VM as following:
<Window x:Class="OutageManagement.Views.MarketAssignmentsView"
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"
mc:Ignorable="d"
Title="Market Selection"
WindowStartupLocation="CenterOwner"
Width="700" Height="850"
DataContext="{Binding MarketAssignmentsVM, Source={StaticResource Locator}}" >
<Grid>
<ItemsControl ItemsSource="{Binding USMarket}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding MarketName}" Height="28"
HorizontalAlignment="Left" Name="lblUSMarketName"
VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
Name="cbUSUsers" VerticalAlignment="Center" MinWidth="140"
ItemsSource="{Binding RelativeSource={RelativeSource
AncestorType=Window}, Path=UserList}"
DisplayMemberPath="UserName"
SelectedValue="{Binding SelectedUserID}"
SelectedValuePath="UserID"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel :
public class MarketAssignmentsViewModel : ViewModelBase
{
#region Data
ObservableCollection<NOCUserViewModel> _userList;
ObservableCollection<MarketAssignmentViewModel> _usMarket;
ObservableCollection<MarketAssignmentViewModel> _caMarket;
#endregion
#region Constructor
public MarketAssignmentsViewModel()
{
GetUserList();
GetMarketAssignments();
}
#endregion
#region Properties
public ObservableCollection<NOCUserViewModel> UserList
{
get { return _userList; }
}
public ObservableCollection<MarketAssignmentViewModel> USMarket
{
get { return _usMarket; }
}
public ObservableCollection<MarketAssignmentViewModel> CAMarket
{
get { return _caMarket; }
}
#endregion
.
.
.
}

The problem is that you're trying to access the UserList as a property of the Window, instead of a property of the Window's DataContext...
Modify the ItemsSource like this:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor
AncestorType=Window}, Path=DataContext.UserList}" ... />
I recommend always looking in the Output window when you have binding problems, you probably would have seen something like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'UserList' property not found on 'object' ''MarketAssignmentsView' (Name='')'.

Related

WPF Cross User Control Data Binding

I have two user controls, one with input fields (Control A) and another one with Data Grid (Control B).
How can i display grid view selected item in textbox in WPF? Is it possible to do it only using WPF or must do inside the program code or both?
Control A with
<UserControl x:Class="PayrollSystem.Controls.EmployeeDetailControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock Text="First Name : " Grid.Column="0" Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<StackPanel Margin="2" Grid.Column="1" Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox x:Name="txtFirstName" Text="{Binding Path=FirstName, Mode=TwoWay}" Width="200"/>
</StackPanel>
Control B with
<UserControl x:Class="PayrollSystem.Controls.EmployeeRecordControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataGrid x:Name="dg" AlternatingRowBackground="Gray" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding Path=FirstName, Mode=TwoWay}"/>
</DataGrid.Columns>
EmployeeRecordControl seems redundant. Instead of that, just use a DataGrid directly, and bind its ItemsSource and SelectedItem to two properties of a view model class like this:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Employee> Employees { get; }
= new ObservableCollection<Employee>();
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get => selectedEmployee;
set
{
selectedEmployee = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedEmployee)));
}
}
}
Then create and bind to the view model like this:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}"
CanUserAddRows="False"/>
<local:EmployeeDetailControl Grid.Column="1"
DataContext="{Binding SelectedEmployee}"/>
</Grid>

How do I pass a property of Datatemplate's Datatype to Caliburn.Micros Message.Attach?

I'm currently in the process of implementing a menu navigation system for a group project. We're using Caliburn.Micro and the MVVM pattern.
The menu is located in the ShellView.xaml and is based on a List<NavigationMenuItem> that has been filtered using the Role property.
public class NavigationMenuItem
{
public IScreen ViewModel { get; set; }
public string IconPath { get; set; }
public string Role { get; set; }
public NavigationMenuItem(IScreen viewModel, string iconPath, string role)
{
ViewModel = viewModel;
IconPath = iconPath;
Role = role;
}
}
The menu is displaying just fine, but now I want to pass the ViewModel-property to a method in the ShellViewModel.cs, such that I can activate a view change. This is how the ShellView.xaml looks so far:
<Window
x:Class="P3GG.Views.ShellView"
xmlns:cal="http://www.caliburnproject.org"
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:P3GG.Views"
xmlns:models="clr-namespace:P3GG.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:templates="clr-namespace:P3GG.Templates"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="Title"
Width="800"
Height="600"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Visibility="{Binding SidebarIsVisible, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"
Grid.Column="0"
Background="#333333"
x:Name="NavigationMenu">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:NavigationMenuItem">
<Button cal:Message.Attach="Handle( <!-- pass content of NavigationMenuItem.ViewModel --> )" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" x:Name="ActiveItem"/>
</Grid>
</Window>
The ShellViewModel.cs currently contains two Handle methods:
public void Handle(Screen screen)
public void Handle(User user)
How do I pass the ViewModel property of a given NavigationMenu to the Handle(Screen) method?
EDIT
Added Handle method per request
public void Handle(Screen screen)
{
if (screen is LoginViewModel)
{
SidebarIsVisible = false;
NotifyOfPropertyChange(() => SidebarIsVisible);
}
else
{
SidebarIsVisible = true;
NotifyOfPropertyChange(() => SidebarIsVisible);
}
ActivateItem(screen);
}
Maybe you can try something like this, as explained here:
<Button cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
This will pass the full view model (DataContext) to your handler.
To pass a specific property of your view model, use the long syntax as indicated here:
<Button Style="{StaticResource BtnSidebar}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Handle">
<cal:Parameter Value="{Binding ViewModel}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
with i defined as
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
You could also change the DataContext property of the Button to the property of interest, like:
<Button DataContext="{Binding ViewModel}" cal:Message.Attach="[Event Click] = [Action Handle($dataContext)]" Style="{StaticResource BtnSidebar}">
<Image Margin="20" Source="{Binding IconPath}"/>
</Button>
so only the correct property gets passed to your hendler, but this feels like a hack.
On click, this will hand-over the DataContext of the Button as parameter of your Handle method, which should be a NavigationMenuItem.

List of objects and ComboBoxes

Here's my problem:
I've got these classes:
public class CsvField
{
public string Content { get; set; }
public CsvField(string content)
{
Content = content;
}
}
public class CsvLine
{
public List<CsvField> Fields = new List<CsvField>();
public int LineNumber;
}
public static class Settings
{
public static List<string> Tags = new List<string>();
public static CsvLine AllHeaders = new CsvLine();
}
What I want to do, is display the ListBox containing every member of Settings.AllHeaders.Fields and a ComboBox containing all members of Settings.Tags list (placed horizontally - a member of AllHeaders on the left and a ComboBox next to it). So if I had 4 headers, I would get a List of those 4 headers and 4 ComboBoxes, each of them next to individual header. Each of these ComboBoxes would contain a list of tags.
So, I defined a DataTemplate:
<Window x:Class="CSV_To_Tags_App.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:CSV_To_Tags_App"
Title="Window2" Height="435" Width="566">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:CsvField}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="HeaderTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap"
VerticalAlignment="Top" Text="{Binding Content}"
/>
<ComboBox HorizontalAlignment="Right" VerticalAlignment="Top" Width="120"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Label Content="Available headers" HorizontalAlignment="Left"
VerticalAlignment="Top"/>
<ListBox x:Name="HeadersListtListBox" HorizontalAlignment="Left"
Height="254" Margin="36,104,0,0" VerticalAlignment="Top" Width="452"
ItemsSource="{Binding}"/>
</Grid>
</Window>
Now, XAML code above is incomplete, because I don't know how to:
1. Bind TextBlock to Settings.AllHeaders.Fields.Content
2. Bind ComboBox to Tags List
The link provided by #MD's does provide the solution to what you are asking in your question, but you have to rearrange things a little bit to see it.
The XAML below will give you what you are asking for, assuming your class that you are binding to is set up properly for data binding (implementing the INotifyPropertyChanged interface). There are lots of examples on this site of how to properly implement that portion.
<Window x:Class="CSV_To_Tags_App.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:CSV_To_Tags_App"
Title="Window2" Height="435" Width="566">
<Grid>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Path=Settings.AllHeadings.Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Content}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Path=Settings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Tags}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>

How to bind a property in the view model of a usercontrol to a property in another usercontrol

I am currently working within a WPF user control using MVVM. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window.Resources>
<ObjectDataProvider x:Key="TabsList" ObjectType="{x:Type local:MainWindowModel}" MethodName="GetTabs"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Source={StaticResource TabsList}}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding Source={StaticResource TabsList}, Path=MyUserControl}"/>
</Grid>
Data provider class is as below
public class MainWindowModel
{
public List<TabInfo> GetTabs()
{
return new List<TabInfo>()
{
new TabInfo() { TabName="Tab1", MyUserControl = new UserControl1()},
new TabInfo() { TabName="Tab2", MyUserControl = new UserControl2()}
};
}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControl MyUserControl { get; set; }
}
And now I have two usercontrols UserControl1 and UserControl2 each has a text box control. I would like to update the Text property of the textbox control in the UserControl1 whenever the Text property of the Textbox control in the UserControl2 is updated. To do this, I tried as below.
UserControl1
<UserControl x:Class="MoreOnBinding2.UserControl1"
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"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20" Text="{Binding Path=UserControl1Text}"/>
</Grid>
UserControl1ViewModel
public class UserControl1VM : ViewModelBase
{
public UserControl1VM()
{
this.UserControl1Text = "10";
}
private string userControl1Text;
public string UserControl1Text
{
get { return userControl1Text; }
set
{
if (userControl1Text != value)
{
userControl1Text = value;
RaisePropertyChanged(() => UserControl1Text);
}
}
}
}
UserControl2
<UserControl x:Class="MoreOnBinding2.UserControl2"
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:MoreOnBinding2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}},
UpdateSourceTrigger=PropertyChanged, Path=UserControl1Text}" />
</Grid>
But it is not working. It seems there is a problem in the RelativeSource tag in the UserControl2. Can someone help on this.
You could use a Dependency Property on your user controls this would give you a bind able property. see link
EDIT 1
Just had another look at your class setup. If you are following the MVVM approach I don't think you should have a reference to your user control in your view model
View Model should be more Like
public Class
{
ObservableCollection<TabInfo> _Tabs = new ObservableCollection<TabInfo>();
public ObservableCollection<TabInfo> Tabs
{
get{return _Tabs;}
set {_Tabs = value;}//Need to implement INotifyPropertyChanged [Link][2]
}
public TabInfo SelectedTab {get;set;}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControlViewModel MyUserControlViewModel{ get; set; }
}
Then in your View
<Window.DataContext>
<vm:MainWindowModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding SelectedTab}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedTab.MyUserControlViewModel"/>
</Grid>
This should give you a list of Tabs and then when one is selected it will set the content of the ContentControl to the MyUserControlViewModel.
You would then need to use some sort of template selector to control the ContentTemplate to load different UserControls into the ContentControl
I haven't tested the code works and you would need to implement the INotifyPropertyChanged on all the public properties so the bindings update as the properties value is changed.
Hope that helps a bit.

Why does my Custom UserControl's dependency property not work with dynamically binding?

My Custom UserControl's dependency property will bind correctly if the value is statically defined in the XAML calling it, like this:
TextBoxText="myName"
but not if the value is bound dynamically itself:
TextBoxText="{Binding ItemTypeIdCode}"
There is my full Code.
Custom UserControl XAML:
<UserControl
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"
mc:Ignorable="d"
x:Class="TestUserControl.UserControl1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
x:Name="UserControl" Height="22" Width="282">
<Grid x:Name="LayoutRoot">
<TextBlock TextWrapping="Wrap" Text="{Binding MyName, ElementName=LayoutRoot}"/>
</Grid>
Custom UserControl Code:
public static readonly DependencyProperty TextBoxTextProperty =DependencyProperty.Register("TextBoxText", typeof(string), typeof(UserControl1));
public string TextBoxText
{
get { return (string)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
In my Main Window XAML :
<Grid x:Name="LayoutRoot">
<Button Content="Button" Height="78" Margin="0,0,93,112" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="94" Click="MyButtonClick"/>
<ListBox x:Name="MyListBox" HorizontalAlignment="Left" Margin="8,8,0,112" Width="192">
<ListBox.ItemTemplate>
<DataTemplate>
<local:UserControl1 HorizontalAlignment="Stretch" Margin="286,37,56,0" VerticalAlignment="Top" d:LayoutOverrides="Height" TextBoxText="{Binding MyName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
In my Main Window Code :
private void MyButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
List<string> MyName = new List<string>();
MyName.Add("Name 1");
MyName.Add("Name 2");
MyName.Add("Name 3");
MyListBox.ItemsSource = MyName;
}
This Code Successfully add my Custom UserControl as ListBoxItem in ListBox But Problem it is not display any text which i Binding.
I don`t understand where i am doing wrong.
You set the DataContext of the UserControl to itself, all bindings will then try to find the path on the UserControl, that is why you should not set the DataContext on UserControls.
You should see a binding error in the Output window of Visual Studio saying something like:
System.Windows.Data Error: 40 : BindingExpression path error: 'ItemTypeIdCode' property not found on 'object' ''UserControl1' (Name='UserControl')'. ...

Categories