I have setup a WPF with several listboxes and an add button:
<Window x:Class="QuickSlide_2._0.Window1"
x:Name="load_style"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:QuickSlide_2._0"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
Title="Load_style" Height="300" Width="300" MinHeight="720" MinWidth="1280" WindowStyle="None" AllowsTransparency="True" Background="#B0000000" AllowDrop="True" WindowStartupLocation="CenterScreen" ShowInTaskbar="False">
<Grid>
<Rectangle Height="720" HorizontalAlignment="Left" Name="rectangle1" Stroke="#00000000" VerticalAlignment="Top" Width="1280" MinHeight="320" MinWidth="380" Fill="DarkGray"/>
<ListBox Height="241" HorizontalAlignment="Left" Margin="502,371,0,0" Name="Presentation_slide_items" VerticalAlignment="Top" Width="199" />
<ListBox Name="subjects_list" Margin="74,154,1039,171" ItemsSource="{Binding ElementName=styles_list, Path=SelectedItem.subjects}"/>
<ListBox Name="sub_subjects_list" Margin="264,154,849,171" ItemsSource="{Binding ElementName=subjects_list, Path=SelectedItem.sub_subjects}"/>
<ComboBox x:Name="styles_list" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="74,112,0,0"/>
<ListBox Name="user_inputs" Margin="502,154,565,421" ItemsSource="{Binding ElementName=sub_subjects_list, Path=SelectedItem.possible_input, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding input}" BorderThickness="0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="button_add_input" Content="Add" HorizontalAlignment="Left" Margin="502,279,0,0" VerticalAlignment="Top" Width="106" Command="{Binding ElementName=sub_subjects_list.add_input} />
</Grid>
Now I want to add an additional user_input to the list in the user_inputs listbox when clicking on the button "button_add_input". I have been searching and it looks like using the "command" option of the button could be the way to go.
This is my class "sub_subject"
public class sub_subject
{
public string short_name { get; set; }
public string name { get; set; }
public bool read_from_db { get; set; }
public string table_name { get; set; }
//public ObservableCollection<string> possible_input { get; set; }
public ObservableCollection<possible_input> possible_input { get; set; }
public sub_subject(string name)
{
this.name = name;
possible_input = new ObservableCollection<possible_input>();
//possible_input = new ObservableCollection<string>();
}
public override string ToString()
{
return this.name;
}
public void add_input()
{
possible_input input = new possible_input();
input.input = "";
possible_input.Add(input);
}
}
I was thinking I can add a function to the class that adds a possible_input to the ObservableCollection and calling this function in the command of the button. But I just cannot figure out how to setup the proper command. Any suggestions?
I assume that sub_subject is the viewmodel (the datacontext) of the window (the view).
In this case you should add a Command class to your project. This is an implementation of the ICommand. You can find an implementation of the ICommand here: https://msdn.microsoft.com/en-us/magazine/dd419663.aspx
After that you will have to create a property in your viewmodel for the command that calls the add_input method. Then, bind your command property to the Command property of the button.
Related
Foreach treeview-item i got an own datagrid. Treeview-items and datagrids are filled by binding.
On textboxes i got a binding to the selected item of the datagrids. But the binding on these textboxes only works with the first datagrid. Every other datagrid doesn't transfer the selecteditem to the textboxes:
Here is the treeview with the datagrid:
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionUnit="FullRow"
SelectedItem="{Binding SelectedMachine, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single">
</DataGrid>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here is the textbox:
<TextBox Text="{Binding PlaceList/SelectedMachine.Name, ValidatesOnDataErrors=True}" />
I am working with MvvmLight. My ViewModel holds the PlaceList:
public ObservableCollection<PlaceModel> PlaceList { get; set; } = new ObservableCollection<PlaceModel>();
public ObjectInspectorViewModel()
{
PlaceList = PlaceModel.GetPlaces(BaseResourcePaths.PlacesCsv);
}
That s my place-model:
public class PlaceModel
{
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine { get; set; }
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
}
I tried something out but at last i dont know how to fix the bug. What s the problem? My suggestion is the property ''SelectedMachine'' inside the place-model...
Here is an example-project (with the additional solution of Sebastian Richter). It shows the problems: https://www.file-upload.net/download-12370581/DatagridTreeViewError.zip.html
I'm quiet sure you forget to implement INotifyPropertyChanged in you class PlaceModel. The problem is after you changed the selection, the Property Placemodel.SelectedMachine will be updated but no event will be fired to populate this change in the View.
Because you use MVVM Light you can derive from ObservableObject which already implements this Interface.
So change your PlaceModel to following code:
public class PlaceModel : ObservableObject
{
private MachineModel _selectedMachine;
public int Id { get; set; }
public string Name { get; set; } = "_CurrentObjectName";
public string Length { get; set; }
public string Width { get; set; }
public string Height { get; set; }
public ObservableCollection<MachineModel> MachinesInPlace { get; set; }
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
public static ObservableCollection<PlaceModel> GetPlaces(string filepath)
{
[...]
}
Edit:
I guess the binding doesn't know which element to bind to from your ObservableCollection (many to one relation) because you set it as the reference in your TextBox.
So try to remove the SelectedMachine property from the Model and add it back to the ViewModel:
class ViewModel : ViewModelBase
{
...
private MachineModel _selectedMachine;
public MachineModel SelectedMachine
{
get
{
return _selectedMachine;
}
set
{
// raises Event PropertyChanged after setting value
Set(ref _selectedMachine, value);
}
}
...
}
Also change your XAML to following code (I used your example project):
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!-- Row #1 -->
<Grid>
<!-- TreeView und DataGrids-->
<TreeView ItemsSource="{Binding Path=PlaceList}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem Header="{Binding Path=Name}">
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<!-- Row #2 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="ID" />
<!-- Textboxen aktualisieren nur bei Auswahl der ersten Datagrid -->
<TextBox Grid.Column="2"
Grid.Row="0"
Text="{Binding SelectedMachine.Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Label Grid.Row="1"
Content="Name" />
<TextBox Grid.Column="2"
Grid.Row="1"
Text="{Binding SelectedMachine.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
The key was to set the correct DataContext for SelectedItem. For this i used following XAML code:
<DataGrid ItemsSource="{Binding MachinesInPlace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding DataContext.SelectedMachine, RelativeSource={RelativeSource AncestorType=Window},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
With this the your example project updates the TextBoxes correctly.
How do I wrap or otherwise display long strings in my listview control. I have been unsuccessful in wrapping, or otherwise displaying long text in my bound ListView control. My xaml page is basically a BOUND FlipView with an ItemTemplate that contains two bound textBlocks and a bound ListView. I can get the TextBlocks to wrap but not the listviewitems. It would seem like such a simple thing yet it eludes me.
Here is a portion of my xaml:
<Page.Resources>
<DataTemplate x:DataType="data:MydataObject" x:Key="MydataObjectTemplate">
<StackPanel HorizontalAlignment="Stretch" Height="596" Width="982">
<TextBlock Name="txtDataObjectId" Text="{Binding dataObject.Id}" Visibility="Collapsed" TextWrapping="WrapWholeWords"/>
<TextBlock FontSize="24" Text="{x:Bind dataObject}" HorizontalAlignment="Center" TextWrapping="WrapWholeWords"/>
<ListView ItemsSource ="{x:Bind theObjectDetails, Mode=OneWay }"
HorizontalAlignment="Stretch"
BorderBrush="Black"
BorderThickness="1"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Stretch">
<ComboBox x:Name="cboCategory" Header="Category" SelectionChanged="cboCategory_SelectionChanged" />
<FlipView x:Name="FlipView1"
ItemsSource="{x:Bind MydataObjects, Mode=OneWay }"
ItemTemplate="{StaticResource MydataObjectTemplate}"
BorderBrush="Black"
BorderThickness="1"/>
</StackPanel>
</Grid>
//c#
public class mydataObject
{
public int Id { get; set; }
public dataObject theObject { get; set; }
public List<dataObjectDetails> theObjectDetails { get; set; }
public override string ToString()
{
return this.theObject.Subject;
}
}
public class dataObjectDetails
{
public int Id { get; set; }
public int dodId{ get; set; }
public string bodyText { get; set; }
public override string ToString()
{
return bodyText ;
}
}
Give the ListView an ItemTemplate, which puts the content in a TextBlock that wraps the text:
<ListView
ItemsSource="{x:Bind theObjectDetails, Mode=OneWay}"
HorizontalAlignment="Stretch"
>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding bodyText}"
TextWrapping="WrapWholeWords"
/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I'm very new to WPF, (I started yesterday) and I'm very confused about data binding. I have a View Model for a Window, which contains a widget called Foo which has its own View Model.
The widget Foo binds its Visibility TwoWay (via a BooleanToVisibilityConverter) to a bool field Visible on its FooViewModel. FooViewModel implements INotifyPropertyChanged and fires a PropertyChanged event whenever Visible is set.
In the Xaml for the Window, it creates a Foo whenever a button is clicked. The Window's view model has another boolean field which is bound TwoWay to the Visibility of its instance of a Foo View. The view model of the WIndow implements INotifyPropertyChanged and fires PropertyChanged events whenever the boolean field is modified.
What I expect this to do is whenever the window's boolean property changes, the visibility of the Foo instance will be set. When this happens I expect the View Model of Foo to be updated, since Foo's visibility binding is two way. When the Foo View Model changes its boolean field I expect the View to change its visibility. Further, I expect the Window view model to be notified that its instance of Foo is no longer visible, and hence the View model of the Window will update its own boolean field. Is this a fundamental misunderstanding?
I post the (obfuscated) code below if it helps shed light on this misunderstanding. Thanks.
Window Xaml
<Window x:Class="XXX.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:XXX.ViewModel"
xmlns:v="clr-namespace:XXX"
Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterOwner" WindowState="Maximized">
<Window.Resources>
<vm:AppViewModel x:Key="AppViewModel"/>
<vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource AppViewModel}}">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Connection" Command="{Binding Authenticate}"/>
<MenuItem Header="_About" Command="{Binding ShowAbout}"/>
<MenuItem Header="_Logout" Command="{Binding Logout}"/>
<MenuItem Header="_Configuration" Command="{Binding Configuration}"/>
<MenuItem Header="_Info" Command="{Binding ShowInfo}"/>
</Menu>
<StackPanel>
</StackPanel>
</DockPanel>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="White"
Padding="10"
BorderThickness="0">
<TextBlock Text="XXX"/>
</Border>
<Grid x:Name="Overlay" Panel.ZIndex="1000" DataContext="{Binding Source={StaticResource AppViewModel}}">
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding Path=Modal, Converter={StaticResource BoolToVisibility}, Mode=OneWay}"
Background="DarkGray"
Opacity=".7" />
<v:Configuration HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Path=ConfigurationVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
<v:Connect HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Path=AuthenticateVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
</Grid>
</Grid>
Window View Model
class AppViewModel : INotifyPropertyChanged
{
[Import(typeof (IEventBus))] private IEventBus _bus;
public AppViewModel()
{
Authenticate = new ForwardCommand(obj => ShowAuthenticationView(), obj => !AuthenticateVisible);
Configuration = new ForwardCommand(obj => ShowConfigurationView(), obj => !ConfigurationVisible);
}
public bool Modal
{
get
{
return AuthenticateVisible || ConfigurationVisible;
}
}
public ICommand Authenticate { get; set; }
public bool AuthenticateVisible { get; set; }
public ICommand ShowInfo { get; set; }
public ICommand ShowAbout { get; set; }
public ICommand Logout { get; set; }
public ICommand Configuration { get; set; }
public bool ConfigurationVisible { get; set; }
private void ShowAuthenticationView()
{
AuthenticateVisible = !AuthenticateVisible;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("AuthenticateVisible"));
PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
}
}
private void ShowConfigurationView()
{
ConfigurationVisible = !ConfigurationVisible;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("ConfigurationVisible"));
PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
UserControl Xaml
<UserControl x:Class="XXX.Connect"
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:vm="clr-namespace:XXX.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<vm:ConnectViewModel x:Key="ViewModel"/>
<vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
</UserControl.Resources>
<Grid Width="280"
Height="173"
DataContext="{Binding Source={StaticResource ViewModel}}"
Visibility="{Binding Path=Visible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"
Background="White">
<Label Content="URL" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,12,0,0" Name="url" VerticalAlignment="Top" Width="169" Text="{Binding Path=Url, Mode=OneWayToSource}" TabIndex="0" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,70,0,0" Name="username" VerticalAlignment="Top" Width="171" Text="{Binding Path=Username, Mode=OneWayToSource}" TabIndex="2" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,41,0,0" Name="password" VerticalAlignment="Top" Width="169" Text="{Binding Path=Password, Mode=OneWayToSource}" TabIndex="1" />
<Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="12,39,0,0" Name="label3" VerticalAlignment="Top" />
<Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="12,68,0,0" Name="label2" VerticalAlignment="Top" />
<DockPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="13">
<Button Content="OK" Height="23" HorizontalAlignment="Left" Margin="5" Name="ok" VerticalAlignment="Top" Width="75" Command="{Binding ConnectCommand}" TabIndex="3" />
<Button Content="Cancel" Height="23" HorizontalAlignment="Left" Margin="5" Name="cancel" VerticalAlignment="Top" Width="75" Command="{Binding CloseCommand}" TabIndex="4" />
</DockPanel>
</Grid>
</UserControl>
UserControl View Model
internal class ConnectViewModel : INotifyPropertyChanged
{
[Import(typeof (IEventBus))] private IEventBus _bus;
public ConnectViewModel()
{
ConnectCommand = new ForwardCommand(obj => Fire(),
obj =>
Visible && !String.IsNullOrEmpty(Url) && !String.IsNullOrEmpty(Url) &&
!String.IsNullOrEmpty(Url));
CloseCommand = new ForwardCommand(obj => Hide(), obj => Visible);
}
public ICommand ConnectCommand { get; set; }
public ICommand CloseCommand { get; set; }
public string Url { get; set; }
public string Username { get; set; }
public string Password { get; set; }
private bool _visible;
public bool Visible
{
get { return _visible; }
set { _visible = value; }
}
private void Fire()
{
_bus.Publish<SessionCreatedEvent, SessionEventHandler>(new SessionCreatedEvent(Url, Username, Password));
Hide();
}
private void Hide()
{
Visible = false;
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Binding always works on properties. So while you Raise Visible in your Hide method, you don't raise it in the actual property. But the property is what the binding engine will set. If you bind this to another dependency property it won't get notified about it.
Btw. whats a TwoWayVisibilityConverter? BooleanToVisibilityConverter is perfectly capable of handling two way bindings.
tl;dr to make two way binding work properly (and in fact even one way binding) you need to implement INotifyPropertyChanged properly which means, if the setter is called raise the property.
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
}
I have a window that uses a viewmodel. This screen contains 2 listviews on a screen. The first listview binds to a propery on my viewmodel called projects. This property returns a model as follows
class ProjectsModel
{
public string ProjectName { get; set; }
public ObservableCollection<ProjectModel> ProjectDetails { get; set; }
}
In this class the ProjectModel looks like the following
public class ProjectModel
{
public string ProjectName { get; set; }
public string ProjectId { get; set; }
public string ProjectFileId { get; set; }
public string ProjectSource { get; set; }
public string ClientCode { get; set; }
public string JobNumber { get; set; }
}
The first listview shows projectname as i expect but I would like it so that when I click on any of the items, the second listview should display its details of the projectdetails property. It almost appears to work has it shows the first items childrean but I beleive that its not being informed that the selected item of the first listview has changed. Ho can I do this? Any ideas would be appreciated becuase Ive been pulling my hair out for hours now!
This is the xaml
<Window x:Class="TranslationProjectBrowser.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TranslationProjectBrowser.ViewModels"
xmlns:local="clr-namespace:TranslationProjectBrowser.Models"
Title="MainWindow" Height="373" Width="452" Background="LightGray">
<Window.DataContext>
<vm:ProjectBrowserViewModel></vm:ProjectBrowserViewModel>
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="projectList" ObjectType="{x:Type vm:ProjectBrowserViewModel}" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource projectList}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="176*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="176*" />
<RowDefinition Height="254*" />
</Grid.RowDefinitions>
<DockPanel LastChildFill="True" >
<TextBlock DockPanel.Dock="Top" Text="Projects" Margin="5,2"></TextBlock>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBox Name="ProjectName" Width="140" Margin="5,2" Height="18" FontFamily="Calibri" FontSize="10"></TextBox>
<Button Height="18" Width="45" HorizontalAlignment="Left" Margin="0,2" FontSize="10" Content="Add" Command="{Binding Path=AddProject}" CommandParameter="{Binding ElementName=ProjectName, Path=Text}"></Button>
<TextBlock Text="{Binding Path=ErrorText}" VerticalAlignment="Center" Margin="6,2" Foreground="DarkRed"></TextBlock>
</StackPanel>
<ListView Name="project" HorizontalAlignment="Stretch" Margin="2" ItemsSource="{Binding Path=Projects}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=ProjectName}" Header="Name" Width="200" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
<DockPanel LastChildFill="True" Grid.Row="1" >
<TextBlock DockPanel.Dock="Top" Text="Project Files" Margin="5,2"></TextBlock>
<ListView HorizontalAlignment="Stretch" Margin="2" ItemsSource="{Binding Path=Projects/ProjectDetails}" IsSynchronizedWithCurrentItem="True" >
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=ProjectName}" Width="200" />
<GridViewColumn Header="Job Number" DisplayMemberBinding="{Binding Path=JobNumber}" Width="100" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Grid>
Your view models should (at least) implement INotifyPropertyChanged. This is how WPF will know when your selction (or other properties) change and the binding needs to be updated.
So you should have something like this:
class ProjectsModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public string ProjectName
{
get
{
return _projectName;
}
set
{
_projectName = value;
NotifyPropertyChanged("ProjectName");
}
}
public ObservableCollection<ProjectModel> ProjectDetails
{
get
{
return _projectDetails;
}
set
{
_projectDetails = value;
NotifyPropertyChanged("ProjectDetails");
}
}
}
In future versions of the .NET framework this gets a lot easier with the "caller info" attributes (http://www.thomaslevesque.com/2012/06/13/using-c-5-caller-info-attributes-when-targeting-earlier-versions-of-the-net-framework/). But as of today this is usually how it's done.
UPDATE
Ok, so based on your comment you need to bind your ListView's SelectedItem property to a property on your view model. You can then Bind your second ListView to that property as well. Something like this:
<ListView ... SelectedItem="{Binding Path=FirstListViewSelectedItem, Mode=TwoWay}" .. >
And then your second list view would be sometihng like this:
<ListView ... ItemsSource="{Binding Path=FirstListViewSelectedItem.ProjectDetails, Mode=OneWay" .. />
I don't see any current management in your code. If you use a CollectionView you will get that for free, see below sample:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Path=ProjectsView}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<ListBox ItemsSource="{Binding Path=ProjectsView/ProjectDetails}" />
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM
{
public VM()
{
List<Project> projectsModel = new List<Project>();
projectsModel.Add(new Project("AAA"));
projectsModel.Add(new Project("BBB"));
projectsModel.Add(new Project("CCC"));
ProjectsView = CollectionViewSource.GetDefaultView(projectsModel);
}
public ICollectionView ProjectsView { get; private set; }
}
public class Project
{
public Project(string name)
{
Name = name;
}
public string Name { get; private set; }
public IEnumerable<string> ProjectDetails
{
get
{
for (int i = 0; i < 3; i++)
{
yield return string.Format("{0}{1}", Name, i);
}
}
}
}
}
I have been searching for a while, and nothing I have tried has resolved the issue. The below code executes without errors, but no data shows in the template.
<UserControl xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="GOReviewSL.UserControls.Announcements"
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"
d:DesignHeight="75" d:DesignWidth="280" xmlns:my="clr-namespace:Local;assembly=Local">
<Grid x:Name="LayoutRoot" Background="White">
<my:Fieldset Height="Auto" HorizontalAlignment="Left" Name="fieldset1" VerticalAlignment="Top">
<StackPanel VerticalAlignment="Top" Orientation="Vertical">
<sdk:DataGrid x:Name="AnnouncementsGrid" ItemsSource="{Binding}">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top" Orientation="Vertical">
<HyperlinkButton x:Name="AnnouncmentTitleLink" FontWeight="Bold" Content="{Binding Title}" Click="AnnouncmentTitleLink_Click" />
<TextBlock x:Name="AuthorText" Text="{Binding Author}" FontSize="10" FontStyle="Italic"/>
<TextBlock x:Name="AnnouncementText" Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</StackPanel>
</my:Fieldset>
<Image Source="/GOReviewSL;component/Images/announcements.png" Height="20" HorizontalAlignment="left" Margin="20,1,0,0" VerticalAlignment="Top"/>
</Grid>
The Announcement class:
public class Announcement
{
public string Author { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public string ModifiedDate { get; set; }
public Announcement()
{
this.Author = string.Empty;
this.Title = string.Empty;
this.Text = string.Empty;
this.ModifiedDate = string.Empty;
}
public Announcement(string author, string title, string text, string modifiedDate)
{
this.Author = author;
this.Title = title;
this.Text = text;
this.ModifiedDate = modifiedDate;
}
}
My binding code:
public Announcements()
{
InitializeComponent();
objController.ListAnnouncementsCompleted += new EventHandler<ListAnnouncementsCompletedEventArgs>(objController_ListAnnouncementsCompleted);
objController.ListAnnouncementsAsync();
}
void objController_ListAnnouncementsCompleted(object sender, ListAnnouncementsCompletedEventArgs e)
{
var objAnnouncements = from el in e.Result
select el;
AnnouncementsGrid.DataContext = objAnnouncements.ToList();
AnnouncementsGrid.ItemsSource = objAnnouncements.ToList();
}
I've changed up that last bit several times. Any help would be greatly appreciated!
Try to bind your Grid to ObservableCollection. First I was using List and had many problems while loading the DataGrid. It is advised to use ObservableCollection in Silverlight instead of List. Why to use ObservableCollection instead of List in Silverlight
using System.Collections.ObjectModel;
ObservableCollection<Announcement> announcementCollection;
public ObservableCollection<Announcement> AnnouncementCollection
{
get { return announcementCollection; }
set
{
announcementCollection = value;
NotifyPropertyChanged("AnnouncementCollection");
}
}
The code should work, although there is a bit of redundant calls in there:
// This is not necessary, and neither is ItemsSource="{Binding}"
//AnnouncementsGrid.DataContext = objAnnouncements.ToList();
AnnouncementsGrid.ItemsSource = objAnnouncements.ToList();
You should check that objAnnouncements.ToList() actually has values. Set a breakpoint on it.
Things to check:
I wonder if your image is covering up the grid. Try commenting it out first.
I don't know what FieldSet is. Does the Datagrid work when it is outside the fieldset?