XAML inconsistent binding - c#

I have a custom UserControl called ReferencedItem. It should take a Guid called ItemId. It is implemented as such:
private static void OnItemIdChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs dpArgs)
{
//Do something
}
public static readonly DependencyProperty ItemIdProperty = DependencyProperty.Register("ItemId", typeof(Guid?), typeof(ReferencedItem), new FrameworkPropertyMetadata(
// use an empty Guid as default value
Guid.Empty,
// tell the binding system that this property affects how the control gets rendered
FrameworkPropertyMetadataOptions.AffectsRender,
// run this callback when the property changes
OnItemIdChanged
));
public Guid? ItemId
{
get { return (Guid?)GetValue(ItemIdProperty); }
set { SetValue(ItemIdProperty, value); }
}
public ReferencedItem()
{
InitializeComponent();
ViewModel = new ReferencedItemCtrlViewModel();
DataContext = ViewModel;
}
The ItemsSource will be made up of Reference objects defined as:
public class Reference
{
public Guid Id { get; set; }
}
Now when binding this ReferencedItem the value is not set as intended. Here is the code I want to work, but does not bind as intended:
<ItemsControl x:Name="ReferenceStack" ItemsSource="{Binding References}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ReferencedItem ItemId="{Binding Id}" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have tried:
<local:ReferencedItem ItemId="128d48f0-f061-49fb-af49-b8e4ef891d03" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
This works as expected, the OnItemIdChanged method is triggered.
<Label Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="30" Width="90"/>
This works as expected, a label is rendered with the Id.
Is there something I'm missing here? From what I can tell the data is available at bind time -- it just doesn't bind under the exact conditions I need it to :)
Thanks for any input!
EDIT:
Here is the code-behind for ReferencedItemList, the first block of XAML posted above:
public partial class ReferencedItemList : UserControl
{
protected ReferencedItemListCtrlViewModel ViewModel;
public ReferencedItemList()
{
InitializeComponent();
ViewModel = new ReferencedItemListCtrlViewModel();
DataContext = ViewModel;
}
public void Load(Guid id, string name)
{
ViewModel.Load(id, name);
//ReferenceStack.ItemsSource = ViewModel.References;
}
}
The commented line has been experimented with in place of the ItemsSource="{Binding References}" that was defined in the XAML.
I don't think I can successfully post the code for ReferencedItemListCtrlViewModel without going down a rabbit hole -- needless to say it has a property References of type ObservableCollection<Reference> where Reference is defined earlier in this post.
ReferencedItem.xaml:
<v:BaseUserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</v:BaseUserControl.Resources>
<StackPanel Orientation="Horizontal">
<Image x:Name="LinkIcon" Visibility="{Binding HasReference, Converter={StaticResource BooleanToVisibilityConverter}}" ToolTip="View Referenced Item" Source="/Images/link.png" Height="18" MouseUp="LinkIcon_MouseUp"/>
<TextBlock x:Name="ReferencedObjectDesc" Text="{Binding ReferenceHierarchy}" FontStyle="Italic" VerticalAlignment="Center" />
</StackPanel>

I just wanted to post the answer (explanation) I came across.
The problem was changing the DataContext of my ReferencedItem user control in the constructor. The view would instantiate a ReferencedItem and alter the DataContext - so once it was time to bind I had already flipped the context from the intended Reference.
There are multiple ways to resolve the timing - all project dependent. Either avoid setting the DataContext all together, set it post binding, or change the context on other items as appropriate.
Much thanks to Sinatr, Andrew Stephens, and Mike Strobel for all mentioning this at one point or another -- just took me some time to actually reach it. I don't think there's a way to assign credit to a comment, but let me know if there is.

Related

ListBox in WPF app is not updating when removing from bound ObservableCollection

I have googled so much on this topic that my hands are bleeding before writing this question. What I have gathered so far is that I know I have to use an ObservableCollection.
The ListBox populates on startup, but the problem occurs when I want to delete/remove an item from the ObservableCollection and have it reflected in the ListBox.
The item is removed from the ObservableCollection but the ListBox does not update anything. I have tried different bindings like Mode and UpdateSourceTrigger.
This is my code so far:
<ListBox Name="projectsListBox" ItemsSource="{Binding Projects}"
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,5,0">
<Image Source="{Binding Icon}" Width="20"/>
<TextBlock Text="{Binding ProjectName}" FontSize="14"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel. The ViewModel inherits from ViewModelBase which in turn inherits from INotifyPropertyChanged.
public class OpenProjectViewModel : ViewModelBase
{
private ObservableCollection<ProjectData> _projects;
public ObservableCollection<ProjectData> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
OnPropertyChanged(nameof(Projects));
}
}
}
}
Code that removes the item from the ObservebleCollection:
Projects
.Remove(Projects
.Where(_ => _.Id == id).Single());
Update: one thing to note is in OpenProjectView I have a OnDelete_Click button, I have instantiated the OpenProjectViewModel which holds the delete logic for the ObservableCollection. The ObservableCollection is instantiated in the constructor. Any thoughts on how to get around this?
private void OnDelete_Click(object sender, RoutedEventArgs e)
{
OpenProjectViewModel openProjectViewModel = new();//<--The ObservableCollection is instantiated in the constructor
openProjectViewModel.DeleteProject(listItem);
}
Did I forget to share anything?
Thank you for your assistance!

x:Bind ViewModel method to an Event inside DataTemplate

I'm basically asking the same question as this person, but in the context of the newer x:Bind.
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Reading the documentation, I found that using Path should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent didn't work either. I'm still getting Object reference not set to an instance of an object, which should mean that it doesn't see the method (but a null).
Image class:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
We have two problems here:
First, trying to directly bind an event to a event handler delegate
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand.
Here's how I would refactor it in two steps:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
"But Gabriel", you say, "that didn't work!"
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
This one is closely related to this question, so I´ll borrow some info from there.
From MSDN, regarding DataTemplate and x:Bind
Inside a DataTemplate (whether used as an item template, a content
template, or a header template), the value of Path is not interpreted
in the context of the page, but in the context of the data object
being templated. So that its bindings can be validated (and efficient
code generated for them) at compile-time, a DataTemplate needs to
declare the type of its data object using x:DataType.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">, you're actually searching for a property named ViewModel on the that models:Image class, which is the DataTemplate's x:DataType. And such a property does not exist on that class.
Here, I can see two options. Choose one of them:
Add that ViewModel as a property on the Image class, and fill it up on the VM.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
With only this, that previous code should work with no need to change anything else.
Drop that x:Bind and go back to good ol'Binding with ElementName.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.

MVVM Navigation Parent and Child Views

I have a GeneralView that is a Parent View and when opens it opens the Parent followed by a Child. I want to implement navigation and keep the buttons in the side (like a UserPage). Lets go to the desired behavior and followed by the code I have now.
How I have implemented the ChildView don't change, stays in the HomeView aka FriendsView.
So description Login > GeneralView (that Opens Immediately in the Home) > Click in About and the childView changes to the AboutView, click in home the HomeView is showed again.
What I have:
GeneralView
<UserControl x:Class="WpfWHERE.View.GeneralView"
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:WpfWHERE.View"
xmlns:ViewModel="clr-namespace:WpfWHERE.ViewModel"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">
<UserControl.DataContext>
<ViewModel:GeneralViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type ViewModel:FriendsViewModel}">
<local:FriendsView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AboutViewModel}">
<local:AboutView />
</DataTemplate>
</UserControl.Resources>
<DockPanel Margin="0,0,0,0">
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MaxWidth="200"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="1" x:Name="userImage" Source="/Resources/Images/profileImage.png" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="161" Width="180" />
<Label Grid.Column="1" x:Name="labelName" Content="NameHere" HorizontalAlignment="Left" Margin="10.4,171,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.536,1.344" Height="26" Width="67"/>
<StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
<MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
</StackPanel>
</Grid>
<ContentControl Content="{Binding Current_ViewModel}" Height="600" Width="600"/>
</StackPanel>
</DockPanel>
GeneralViewModel
class GeneralViewModel:AViewModel
{
public GeneralViewModel()
{
this.AddViewModel(new FriendsViewModel() { DisplayName = "Friends", InternalName = "FriendsViewModel" });
this.AddViewModel(new AboutViewModel() { DisplayName = "About", InternalName = "AboutViewModel" });
this.Current_ViewModel = this.GetViewModel("FriendsViewModel");
}
}
AViewModel Interface
public abstract class AViewModel : ViewModelBase
{
public string Name { get; set; }
public RelayCommand<string> SelectViewCommand { get; set; }
public AViewModel()
{
SelectViewCommand = new RelayCommand<string>(OnSelectViewCommand);
}
private static ObservableCollection<ViewModelBase> _ViewModels;
public static ObservableCollection<ViewModelBase> ViewModels
{
get { return _ViewModels; }
set { _ViewModels = value; }
}
public void AddViewModel(ViewModelBase viewmodel)
{
if (ViewModels == null)
ViewModels = new ObservableCollection<ViewModelBase>();
if (!ViewModels.Contains(viewmodel))
ViewModels.Add(viewmodel);
}
public ViewModelBase GetViewModel(string viewmodel)
{
return ViewModels.FirstOrDefault(item => item.InternalName == viewmodel);
}
private void OnSelectViewCommand(string obj)
{
switch (obj)
{
case "ExitCommand":
Application.Current.Shutdown();
break;
default:
this.Current_ViewModel = this.GetViewModel(obj);
break;
}
}
private ViewModelBase _Current_ViewModel;
public ViewModelBase Current_ViewModel
{
get { return _Current_ViewModel; }
set { _Current_ViewModel = value; OnPropertyChanged("Current_ViewModel"); }
}
}
Try this....
Change this...
<StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
<MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
</StackPanel>
To this...
<StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
<MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding SelectViewCommand}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
<MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding SelectViewCommand}" CommandParameter="AboutViewModel" x:Name="About"/>
</StackPanel>
Note that I have removed 'DataContext' and 'ElementName' from your MenuItems
INotifyProperty is already implemented in ViewModelBase
UPDATE 1
The problem was with ElementName=GeneralView... an Element with that Name does not exist. you could have added x:Name=”GeneralView” to the top of your Base_View XAML BUT there is no need as your ContentControl was bound to Current_ViewModel in the Base_ViewModel anyway....
When you press a button to 'change Views' you are actually changing the value of the property that your ContentControl is bound to, so you have to call the correct SelectViewCommand function in the SAME instance of the class that your ContentControl is bound too....
In the demo you'll see that in the 'LogOn_View' I call
Command="{Binding DataContext.SelectViewCommand, ElementName=Base_V}", CommandParameter="Main_ViewModel"
Here I am calling the SelectViewCommand in the Base_V, That's because I want to change the view that is displayed in the Base_V's ContentControl
In Main_View I call
Command="{Binding SelectViewCommand}", CommandParameter="MainV1_ViewModel"
Here I am calling the SelectViewCommand in the Main_ViewModel, That's because I want to change the View displayed in the ManiView's ContentControl
For anyone that wants the demo code that I am talking about above, you can find it here...
http://www.mediafire.com/download/3bubiq7s6xw7i73/Navigation1.rar
Also, a little update to the code... replace the AddViewModel function in AviewModel with this.....
public void AddViewModel(ViewModelBase viewmodel)
{
if (ViewModels == null)
ViewModels = new ObservableCollection<ViewModelBase>();
var currentVNs = (from vms in ViewModels where vms.InternalName == viewmodel.InternalName select vms).FirstOrDefault();
if (currentVNs == null)
ViewModels.Add(viewmodel);
}
OK so... After some more thought.... The problem with a lot of tutorial code on the Internet is that it is quite basic. The implementation of the relationship between the Data (Model) <=> View, seems to be a matter of taste. HOWEVER I have only EVER seen it done badly. Personally speaking, most of the WPF applications I have worked on have been absolute rubbish, unmaintainable and unmanageable spaghetti code....You can clearly see that the original developer has 'Learnt' as they went along, everything is everywhere and there is no consistency.... As I normally come on to a project half way though I have no choice other than to continue with the way its been started, so I never gave it much thought, until now!!.
My own understanding is that the 'Model' defines the Data Structure, the 'View' displays that Data to the user and the ViewModel is the bit in-between that translates the Data (Model) into something that can be displayed to the user.
In the ViewModel you would simply have ObservableCollections (Lists of Data (Models)) and single instances of data (a single instance of a Model). The 'View' then Binds to the ObservableCollections (and single Model instances) to display that data (using the magic of XAML and Templating etc.)....
As for passing an object (Class) to the ViewModel, I don't think you would actually need to do that. You would simply create a property in your ViewModel that represents the Data you want to display, then retrieve the Data from a 'Source' as and when needed (typically when the View is displayed but could also be periodicity on a timer\thread or something)....
The main problem with my Demo code (download) was that the constructor of the ViewModels was NEVER being called after the application had started so there was no way of refreshing the Properties, in the ViewModel, from a Data source....
There may very well be a better way to do this BUT I have fixed that problem by introducing an Event called Initialize in the 'ViewModelBase'
public delegate void MyEventHadler();
public event MyEventHadler Initialize;
public void InitializeFunction()
{
if (Initialize != null)
Initialize.Invoke();
}
that Event can then be subscribed to in the constructor of each ViewModel
public MainV2_ViewModel()
{
this.Initialize += MainV2_ViewModel_Initialize; // our new Event
}
And the Event Stub that is called when we navigate to this ViewModel\View from somewhere else....
private void MainV2_ViewModel_Initialize()
{
// So here we are retrieving a List of All users from the WCF Service
this.AllUsers = new ObservableCollection<ServiceReference1.User>( DataAccessLevel.sr1.GetAllUsers());
// Now our AllUsers property has been updated and the View will display the new data
}
Now when you switch from one ViewModel\View to another, the Initialize Event is called\raised in AviewModel\Current_ViewModel Property Setter
private ViewModelBase _Current_ViewModel;
public ViewModelBase Current_ViewModel
{
get { return _Current_ViewModel; }
set {
_Current_ViewModel = value;
// the Constructor of the ViewModel never gets called more that once on App Start
// so we have to implement/raise our own event when changing from one View to another.
if (Current_ViewModel != null)
Current_ViewModel.InitializeFunction(); // InitializeFunction will fire the event in this ViewModel, we can now initialise the properties.
OnPropertyChanged("Current_ViewModel"); }
}
this will then fire the 'Initialize' Event in the ViewModel that is being Switched too, giving us the opportunity to refresh the Data.....
The Demo Code is now a fully functioning application (still needs work of course). It provides the following functionality...
Register a New User :: Create a new user with UserName and Password
Log a User On :: Log on existing User with UserName and Password
Recover a User (forgotten password) :: Reset the Password of a registered User (using existing UserName and new Password)
In addition, error messages are returned from the WCF Service that are then displayed in an Error View (See LogOnError_ViewModel and LogOnError_View) when something goes wrong (incorrect Password etc.)
find the Demo Code here....
http://www.mediafire.com/download/881yo6reo55tm8l/Navigation1_WCF_EF_05052016.rar
The Demo Code comes with a WCF Service and a WPF application, because it uses a WCF Service it can only be run from the Visual Studio IDE (Unless you deploy the WCF service to IIS). The WCF service uses Entity Framework and (should) create\attach a database to store User Data when you first register a new user...
Maybe you can improve on the code or get some ideas.....

Databinding a class with boolean and text to XMAL

I submitted this accidently, I fixed it myself and wasn't going to submit after writing out the question. But have learnt from the comments, thanks!
I am trying to create a simple todo app in win8 and eventually want to hock it into ToDoIst API.
I have created a simple task class to try and get my head around the databinding however I just can not get it to do what I want to do. I have used listboxes and other basic form elements.
task.cs
class task
{
private string content;
private bool complete;
public string Content
{
get {return content;}
set { content = value; }
}
public bool Complete
{
get { return complete; }
set { complete = value; }
}
public task(string content)
{
Content = content;
Complete = false;
}
}
MainPage.xaml
And at the moment my XAML looks like this.
<GridView HorizontalAlignment="Left" Margin="482,190,0,0" VerticalAlignment="Top" Width="400" Height="500">
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Content}"/>
<RadioButton/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GridView>
I have put in some dummy data, 4 elements and when I run it, it comes up with 4 boxes with radio buttons however no text (there is space for the text) I am not sure how I would bind the bool?
I can not see what I am doing wrong. If anyone could help and point me in the right direction, I have searched a fair amount of tutorials and just can not figure it out.
Your code looks a little strange, maybe this is what you want:
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding ToDoItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="ToDos" Content="{Binding Content}" IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Do you really want radiobuttons? I think you want Checkboxes, the difference is that when you use radiobuttons only one in a group can be 'checked'
I used this code behind to have a datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ToDoItems = new ObservableCollection<TodoItem>(new List<TodoItem>
{
new TodoItem("Content1"),
new TodoItem("Content2")
});
this.DataContext = this;
}
public ObservableCollection<TodoItem> ToDoItems { get; set; }
}
I changed the name of task to ToDoItem Task is already a class in the framework and might cause confusion.
For the RadioButton IsChecked is the property to bind to a bool property:
<RadioButton IsChecked="{Binding Path=Complete}"/>
Your text is most likely not showing up because you haven't set up any change notifications and the binding is happening before you set the Content values. Using the INotifyPropertyChanged interface is the most common and usually the easiest way to do this.
Like John already mentioned you should really let the window containing your listview implement the INotifyPropertyChanged interface. And to set the data context of your window like Johan said. It is important that you call the propertychanged method in each setter of a property. It is also useful to use an ObservableCollection as ItemSource of your listview. Try to create an instance of ObservableCollection, create a property for it calling propertychanged method in its setter an set rhe ItemSource of your listview to the property. Do not forget to also call propertychanged whenever you add or remove items from the collection

Passing parameters to CollectionViews

I am attempting to write my first implementation of databinding in a WPF form.
I have got to the point where I can successfully retrieve data to populate my combobox with the required entries but am stuck on how I would go about populating a dependent ListView given the current selection of the Combobox.
To explain my problem better this is the current view backing my application.
class SchoolBookViewModel
{
public SchoolBookViewModel() { }
//Problem is here... How do I pass in the SelectedSchoolClassID?
public CollectionView ClassBookListEntries(Guid SelectedSchoolClassID)
{
return new CollectionView(SchoolQuoteList.GetSchoolClassQuoteListBySchoolClassID(SelectedSchoolClassID, 2011, "MyConnectionString"));
}
public CollectionView SchoolEntries
{
get
{
return new CollectionView(SchoolList.GetSchoolList("MyConnectionString"));
}
}
}
And this is the XAML
<StackPanel Height="449" HorizontalAlignment="Left" Margin="12,12,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="650" DataContext="{Binding}">
<Label Content="School:" Height="28" Name="lblSchoolName" Width="651" />
<ComboBox Height="23" Name="cmbSchoolNames" Width="644" DisplayMemberPath="SchoolName" ItemsSource="{Binding Path=SchoolEntries}" SelectedValuePath="SelectedSchoolClassID" SelectionChanged="cmbSchoolNames_SelectionChanged" />
<Label Content="Class booklist:" Height="29" Name="label1" Width="651" />
<ListView Height="163" Name="lblClassBookList" Width="645" ItemsSource="{Binding Path=ClassBookListEntries}" DisplayMemberPath="QuoteReference" />
</StackPanel>
And in the Window_Loaded method I call
stackPanel1.DataContext = new Views.SchoolBookViewModel();
Am I even on the right track? Any guidance would be appreciated.
To get the ComboBox's selection back into the ViewModel you need a property to bind to one of its selection properties. You can also get rid of the explicit CollectionViews if you're not doing anything with them. By just binding to the collections directly, ICollectionView instances will be created and managed for you automatically. Try structuring your VM like this:
class SchoolBookViewModel : INotifyPropertyChanged
{
private SchoolList _selectedSchoolClass;
public SchoolList SelectedSchoolClass
{
get { return _selectedSchoolClass; }
set
{
_selectedSchoolClass = value;
ClassBookListEntries = SchoolQuoteList.GetSchoolClassQuoteListBySchoolClassID(_selectedSchoolClass.Id, 2011, "MyConnectionString");
NotifyPropertyChanged("SelectedSchoolClass");
}
}
private SchoolQuoteList _classBookListEntries;
public SchoolQuoteList ClassBookListEntries
{
get { return _classBookListEntries; }
set
{
_classBookListEntries = value;
NotifyPropertyChanged("ClassBookListEntries");
}
}
private SchoolList _schoolEntries;
public SchoolList SchoolEntries
{
get
{
if (_schoolEntries == null)
_schoolEntries = SchoolList.GetSchoolList("MyConnectionString");
return _schoolEntries;
}
}
...
}
In general it's better to not set explicit Width and Height values and instead let the layout system size elements for you. You can also get rid of the DataContext="{Binding}" - this is redundant as DataContext is inherited and {Binding} means the value of the DataContext itself. Here's the XAML cleaned up and bound to the new properties from above:
<StackPanel HorizontalAlignment="Left" Margin="12,12,0,0" x:Name="stackPanel1" VerticalAlignment="Top">
<Label Content="School:" x:Name="lblSchoolName" />
<ComboBox x:Name="cmbSchoolNames" DisplayMemberPath="SchoolName" ItemsSource="{Binding Path=SchoolEntries}"
SelectedItem="{Binding SelectedSchoolClass}" />
<Label Content="Class booklist:" x:Name="label1" />
<ListView x:Name="lblClassBookList" ItemsSource="{Binding Path=ClassBookListEntries}" DisplayMemberPath="QuoteReference" />
</StackPanel>

Categories

Resources