Dependency Property WPF Always Returning NULL - wpf

I have a UserControl called SharpComboBox. I am using MVVM model to populate the SharpComboBox with Categories. For that I need to set the ItemsSource property. Here is the usage of the SharpComboBox control.
<sharpControls:SharpComboBox ItemsSource="{Binding Path=Categories}" Grid.Column="1" Grid.Row="1" DisplayMemberPath="Title">
</sharpControls:SharpComboBox>
The Window is called the AddBook.xaml and here is the code behind:
public AddBooks()
{
InitializeComponent();
this.DataContext = new AddBookViewModel();
}
And here is the implementation of the AddBookViewModel.
public class AddBookViewModel
{
private CategoryRepository _categoryRepository;
public AddBookViewModel()
{
_categoryRepository = new CategoryRepository();
}
public List<Category> Categories
{
get
{
return _categoryRepository.GetAll();
}
}
And finally here is the SharpComboBox control:
<StackPanel Name="stackPanel">
<ComboBox x:Name="comboBox">
<ComboBox.ItemTemplate>
<DataTemplate>
<ItemsControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
</Grid>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding Path=Title}" Margin="10" />
<Image Grid.Column="1" Margin="10" Grid.Row="0" Width="100" Height="100" Stretch="Fill" Source="{Binding Path=ImageUrl}">
</Image>
</ItemsControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
And here is the code behind:
public partial class SharpComboBox : UserControl
{
public static DependencyProperty ItemsSourceProperty;
public SharpComboBox()
{
InitializeComponent();
this.DataContextChanged += new System.Windows.DependencyPropertyChangedEventHandler(SharpComboBox_DataContextChanged);
ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof (IEnumerable),
typeof (SharpComboBox), null);
comboBox.ItemsSource = ItemsSource;
}
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
void SharpComboBox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
}
For some reason the ItemsSource property is always null.
UPDATED:
void SharpComboBox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
var binding = new Binding();
binding.Source = this.DataContext;
**binding.Path = new PropertyPath("Categories");**
comboBox.SetBinding(ComboBox.ItemsSourceProperty, binding);
}

kek444 is very close, but there is one critical element missing. I've noticed your ViewModel doesn't implement INotifyPropertyChanged. This will prevent bindings from automatically refreshing when you've set that property. So, as kek444 mentioned, you are intially binding to null (because it's early) and then when you set it, you aren't informing your View of the change. It's pretty simple to change, though.
public class AddBookViewModel : INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
public AddBookViewModel()
{
_categoryRepository = new CategoryRepository();
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Categories");
}
}
...
}
Anytime you change your backing store (CategoryRepository), you'll want to do this. There may be some additional complication here depending on your your repository is implemented, but this information ought to at least explain what is going on.
As a practice, I generally create a base ViewModel class that implements INotifyPropertyChanged so I can add a few helper methods that wrap that PropertyChanged logic up... I just call OnPropertyChanged("MyProp"); and that's it.
One other thing that might help you is that bindings will report to the debug output if you configure them correctly: Debugging WPF Binding
Hope this helps.

You cannot simply set the comboBox.ItemsSource from your property once in the constructor, who knows how early that happens. You need to set a binding between those two properties.

Related

Can't bind DependencyProperties from UserControl code behind to ViewModel

I'm trying to create a UserControl in my WPF project which I want should have a DependencyProperty that I can bind to in the parent. The project is written as MVVM and I'm using Caliburn micro.
I really want to write clean and maintainable code using MVVM, so I want my UserControls to utilize viewmodels as much as possible and code behind as little as possible.
The problem is that I'm unsuccessful in getting the binding between the parent and the UserControl viewmodel to work correctly.
MyUserControl:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
// If no Datacontext is set, binding between parent property and textbox text works - one way only (set from parent)!.
// -
// If Datacontext is set to this, bindings with properties in MyUserControlView code behind works.
//DataContext = this;
// If Datacontext is set to MyUserControlViewModel, binding between MyUserControlViewModel and MyUserControlView works, but not with parent.
DataContext = new MyUserControlViewModel();
}
public string ProjectNumber
{
get { return (string)GetValue(MyUserControlValueProperty); }
set { SetValue(MyUserControlValueProperty, value); }
}
public static readonly DependencyProperty MyUserControlValueProperty =
DependencyProperty.Register("ProjectNumber", typeof(string), typeof(MyUserControlView), new PropertyMetadata(null, new PropertyChangedCallback(OnProjectNumberUpdate)));
private static void OnProjectNumberUpdate(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var view = d as MyUserControlView;
view.ProjectNumberText.Text = e.NewValue as string;
}
}
MyUserControl code behind:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding ProjectNumber}" />
</StackPanel>
<TextBox Name="ProjectNumberText" Text="{Binding ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</StackPanel>
MyUserControl ViewModel:
public class MyUserControlViewModel : Screen
{
private string _projectNumber;
public string ProjectNumber
{
get { return _projectNumber; }
set
{
_projectNumber = value;
NotifyOfPropertyChange(() => ProjectNumber);
}
}
}
Parent view:
<StackPanel>
<local:MyUserControlView ProjectNumber="{Binding ParentProjectNumber}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="In parent: "/>
<TextBlock Text="{Binding ParentProjectNumber}" />
</StackPanel>
</StackPanel>
Parent ViewModel:
public class ShellViewModel : Screen
{
public ShellViewModel()
{
ParentProjectNumber = "Hello from parent!";
}
private string _parentProjectNumber;
public string ParentProjectNumber
{
get { return _parentProjectNumber; }
set
{
_parentProjectNumber = value;
NotifyOfPropertyChange(() => ParentProjectNumber);
}
}
}
I know I'm probably way off here, but I have no idea what to do to get the bindings to work correctly.
Is there a better way to bind between a DependencyProperty and a viewmodel? Can I put the DP in the viewmodel somehow?
Here is the entire project solution: https://github.com/ottosson/DependencyPropertyTest
don't change UserControl.DataContext from inside UserControl. it can and will create issues later.
use proper name for DP (ProjectNumberProperty and corresponding ProjectNumber) and add BindsTwoWayByDefault to metadata:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
}
public string ProjectNumber
{
get { return (string)GetValue(ProjectNumberProperty); }
set { SetValue(ProjectNumberProperty, value); }
}
public static readonly DependencyProperty ProjectNumberProperty = DependencyProperty.Register
(
"ProjectNumber",
typeof(string),
typeof(MyUserControlView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
}
fix bindings in xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding Path=ProjectNumber, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
<TextBox Text="{Binding Path=ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
that should do it.
btw, "clean and maintainable code using MVVM" and "want my UserControls to utilize viewmodels as much as possible" sort of contradict each other.
also nothing wrong with code-behind in UserControls as long as that code handles only view functionality. for example: DataGrid source code contains 8000+ LoC

How to Register an Attached Property of Textbox Text So Datagrid Can Update on Change

I have a textbox and a datagrid like so:
<Page
TextElement.FontSize="14" FontFamily="Segoe UI"
Title="Delivery View">
<Page.Resources>
<xcdg:DataGridCollectionViewSource x:Key="firstNameDataSource"
Source="{Binding Path=Accessor.Views[FirstNameView].SourceCollection}"
AutoFilterMode="And"
DistinctValuesConstraint="Filtered">
<xcdg:DataGridCollectionViewSource.ItemProperties>
<xcdg:DataGridItemProperty Name="FirstName" CalculateDistinctValues="False"/>
</xcdg:DataGridCollectionViewSource.ItemProperties>
</xcdg:DataGridCollectionViewSource>
</Page.Resources>
<ScrollViewer Name="pendingScroll" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<DockPanel Name="pnlMainPanel" LastChildFill="True" Style="{StaticResource panelBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="18" Text="Pending Guests" Margin="0,1,3,1" Foreground="SteelBlue" HorizontalAlignment="Left"/>
<TextBox Name="txtFirstNameFilter" Grid.Row="1" >
</TextBox>
<xcdg:DataGridControl x:Name="gridPendingGuests" Margin="5,0,5,1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinHeight="100"
MinWidth="200"
CellEditorDisplayConditions="None"
EditTriggers="None"
ItemScrollingBehavior="Immediate"
AutoCreateColumns="False"
SelectionMode="Single"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource firstNameDataSource}}">
<xcdg:DataGridControl.View>
<xcdg:TableView ShowRowSelectorPane="False"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Columns>
<xcdg:Column x:Name="FirstName" FieldName="FirstName" Title="First Name" Width="150" />
</xcdg:DataGridControl.Columns>
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange/>
</i:Interaction.Behaviors>
</xcdg:DataGridControl>
</Grid>
</DockPanel>
</ScrollViewer>
</Page>
In the datagrid, you have a collection of first names. This works perfectly. The display is good. As you can see, I added an Interactions.Behavior class which currently handles a filter with a hard coded value when the user clicks on the datagrid with their mouse. The filtering works fine. If there is a first name of "John", that record is removed from view, leaving all other records in place.
Here is that code:
using System.Windows.Interactivity;
using System.Windows;
using Xceed.Wpf.DataGrid;
using System;
namespace Some.Namespace.Behaviors
{
public class UpdateDataGridOnTextboxChange : Behavior<DataGridControl>
{
protected override void OnAttached()
{
AssociatedObject.MouseUp += AssociatedObjectOnMouseUp;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseUp -= AssociatedObjectOnMouseUp;
base.OnDetaching();
}
private void AssociatedObjectOnMouseUp(object sender, RoutedEventArgs routedEventArgs)
{
var items = AssociatedObject.Items;
items.Filter = CollectionFilter;
}
private bool CollectionFilter(object item)
{
System.Data.DataRow dr = item as System.Data.DataRow;
//set the ItemArray as Guest
Guest guest = SetGuest(dr);
if (guest.FirstName.Equals("John"))
{
return false;
}
return true;
}
private Guest SetGuest(System.Data.DataRow dr)
{
Guest guest = new Guest();
guest.FirstName = dr.ItemArray[0].ToString();
return guest;
}
public class Guest
{
public string FirstName { get; set; }
}
}
}
This works as expected. Again, when the user clicks on the datagrid, the filter filters out the users with the First Name of "John".
What I WANT to have happen is for the user to be able to type a first name in the txtFirstNameFilter Textbox and the datagrid to then filter the records that contain the text in the first name, keeping them visible and the others without that first name to not be visible.
The way I can do it is with an attached property of the Textbox TextChanged property? That's a question, because I don't know how to do an attached property and then how to make sure that when that attached property actually changes, call the AssociatedObjectOnMouseUp method to run the filtering.
System.Windows.Interactivity.Behavior<T> inherits from DependencyObject. So give it a dependency property and bind that.
public class UpdateDataGridOnTextboxChange : Behavior<DataGrid>
{
#region FilterValue Property
public String FilterValue
{
get { return (String)GetValue(FilterValueProperty); }
set { SetValue(FilterValueProperty, value); }
}
public static readonly DependencyProperty FilterValueProperty =
DependencyProperty.Register(nameof(FilterValue), typeof(String), typeof(UpdateDataGridOnTextboxChange),
new FrameworkPropertyMetadata(null, FilterValue_PropertyChanged));
protected static void FilterValue_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as UpdateDataGridOnTextboxChange).OnFilterValueChanged(e.OldValue);
}
private void OnFilterValueChanged(object oldValue)
{
// Do whatever you do to update the filter
// I did a trace just for testing.
System.Diagnostics.Trace.WriteLine($"Filter value changed from '{oldValue}' to '{FilterValue}'");
}
#endregion FilterValue Property
/*****************************************
All your code here
*****************************************/
}
XAML:
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange
FilterValue="{Binding Text, ElementName=txtFirstNameFilter}"
/>
</i:Interaction.Behaviors>
You should rename it, though. It's got nothing to do with text boxes. You could bind FilterValue to a viewmodel property, or the selected value in a ComboBox, or whatever.
Update
OP's having trouble with the binding only updating FilterValue when the text box loses focus. This isn't what I'm seeing, but I don't know what's different between the two.
There isn't any UpdateTargetTrigger property of Binding, but you can swap the source and the target when both are dependency properties of dependency objects. This works for me:
<TextBox
x:Name="txtFirstNameFilter"
Text="{Binding FilterValue, ElementName=DataGridFilterThing, UpdateSourceTrigger=PropertyChanged}"
/>
<!-- snip snip snip -->
<i:Interaction.Behaviors>
<local:UpdateDataGridOnTextboxChange
x:Name="DataGridFilterThing"
/>
</i:Interaction.Behaviors>

Binding Error in runtime clearing ObservableCollection

I have an ObservableCollection that works perfectly, but I can't remove a binding error that appears in runtime when I clear this ObservableCollection:
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=Icon; DataItem='NamedObject' (HashCode=40835417); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
I made a small code to reproduce my problem that I show below: (View):
<Button Height="40" Width="40" Click="Button_Click"></Button>
<ListBox ItemsSource="{Binding ProductList}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Height="32" Source="{Binding Icon}" Stretch="Fill" Width="32"/>
<Label Grid.Row="1" Content="{Binding Description}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public ObservableCollection<Product> ProductList { get; set; }
public void TestList()
{
ProductList = new ObservableCollection<Product>();
ProductList.Add(new Product("Product1", "pack://application:,,,/Product1.png"));
ProductList.Add(new Product("Product2", "pack://application:,,,/Product2.png"));
ProductList.Add(new Product("Product3", "pack://application:,,,/Product3.png"));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ProductList.Clear();
ProductList.Add(new Product("Product4", "pack://application:,,,/Product4.png"));
}
And my product class:
public class Product : INotifyPropertyChanged
{
#region "## INotifyPropertyChanged Members ##"
public event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { this._propertyChanged += value; }
remove { this._propertyChanged -= value; }
}
protected virtual void OnPropertyChanged(string propertyName)
{
App.Current.Dispatcher.BeginInvoke((Action)delegate
{
if (this._propertyChanged != null)
this._propertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}
#endregion
public string Description
{
get { return this.description; }
set
{
this.description = value;
this.OnPropertyChanged("Description");
}
}
private string description;
public BitmapImage Icon
{
get { return this.icon; }
set
{
this.icon = value;
this.OnPropertyChanged("Icon");
}
}
private BitmapImage icon;
public Product(string desc, string iconPath)
{
Description = desc;
BitmapImage bi = new BitmapImage(new Uri(iconPath));
bi.Freeze();
Icon = bi;
}
}
The error appears when I click the button and the following line is executed:
ProductList.Clear();
I have done many tests:
Individually delete items from the list
Use a Fallback and TargetNullValue:
<Image Height="32" Source="{Binding Icon, FallbackValue='pack://application:,,,/transparent.png', TargetNullValue='pack://application:,,,/transparent.png'}" Stretch="Fill" Width="32"/>
Any ideas?
Try removing the TargetNullValue, and FallbackValue and see if it still occurs? If it does not there is a problem with your URI to the file.
Also keep in mind if you are referencing transparent.png often you are loading the same image into memory many times. Instead consider adding a line to your ResourceDictionary like so:
<BitmapImage UriSource="/MyApp;component/Images/transparent.png" x:Key="Transparent" PresentationOptions:Freeze="True" />
Then using in the XAML like so:
<Image Height="32" Source="{Binding Icon, FallbackValue={StaticResource:Transparent}, TargetNullValue={StaticResource:Transparent}" Stretch="Fill" Width="32"/>
This change will load your image once but use it many places decreasing memory pressure.

WPF: How to Create ViewModel Within the View

Essentially, I have a markup issue. I have come up with a few solutions but I can't help but feel like this should be simpler. Rather than lead you down my convoluted path I thought I would share the simplest implementation and ask how you would address it.
MainPage.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<!--Additional Columns-->
</Grid.ColumnDefinitions>
<!--Row Definitions-->
<Label Grid.Row="0" Grid.Column="0" Content="Vin:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2" Command="{Binding CreateVehicleCommand}" CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Manufacturer:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding Vehicle.Manufacturer, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
Given the example above, how can I get the Contents of the Grid into a View given the constraint that the Command to create the vehicle is outside of the DataContext to be created(Vehicle)?
If you do wish to look at my specific attempt, that question is here UserControl's DependencyProperty is null when UserControl has a DataContext
how can I get the Contents of the Grid into a View given the
constraint that the Command to create the vehicle is outside of the
DataContext to be created(Vehicle)?
This feels like a race condition more than an MVVM problem. I will address the issue first but make a secondary suggestion after.
There are no reasons in which a ViewModel cannot contain another viewmodel as a reference and that reference is bound to using the INotifyPropertyChanged mechanisim.
Or that your xaml (view) page contains a static reference to a ViewModel which the page (view) does not directly use in its DataContext, but that a certain control cannot bind to that static outside of the data context of the containing control.
Either way one can provide access (as also mentioned in the response to the other post you provided) by pointing to itself to get data or to provide an alternate plumbing which gets the data.
Or you can flatten your viewmodel to contain more information and handle this IMHO race condition so that this situation doesn't arise and the control as well as the grid can access information in a proper format.
I can't fully address the problem because you are more aware of the design goals and hazards which now must be worked around.
I've come up with something, I'm relatively happy with. This has saved me from creating 100s of composite ViewModel's and while it does introduce some unnecessary complexity it does dramatically reduce the amount copy/paste code I need to write.
VMFactoryViewModel.cs
public class CreatedViewModelEventArgs<T> : EventArgs where T : ViewModelBase
{
public T ViewModel { get; private set; }
public CreatedViewModelEventArgs(T viewModel)
{
ViewModel = viewModel;
}
}
public class VMFactoryViewModel<T> : ViewModelBase where T : ViewModelBase
{
private Func<string, T> _createViewModel;
private RelayCommand<string> _createViewModelCommand;
private readonly IDialogService _dialogService;
/// <summary>
/// Returns a command that creates the view model.
/// </summary>
public ICommand CreateViewModelCommand
{
get
{
if (_createViewModelCommand == null)
_createViewModelCommand = new RelayCommand<string>(x => CreateViewModel(x));
return _createViewModelCommand;
}
}
public event EventHandler<CreatedViewModelEventArgs<T>> CreatedViewModel;
private void OnCreatedViewModel(T viewModel)
{
var handler = CreatedViewModel;
if (handler != null)
handler(this, new CreatedViewModelEventArgs<T>(viewModel));
}
public VMFactoryViewModel(IDialogService dialogService, Func<string, T> createViewModel)
{
_dialogService = dialogService;
_createViewModel = createViewModel;
}
private void CreateViewModel(string viewModelId)
{
try
{
OnCreatedViewModel(_createViewModel(viewModelId));
}
catch (Exception ex)
{
_dialogService.Show(ex.Message);
}
}
}
VMFactoryUserControl.cs
public class VMFactoryUserControl<T> : UserControl where T : ViewModelBase
{
public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<T>), typeof(VMFactoryUserControl<T>));
public VMFactoryViewModel<T> VMFactory
{
get { return (VMFactoryViewModel<T>)GetValue(VMFactoryProperty); }
set { SetValue(VMFactoryProperty, value); }
}
}
GenericView.xaml
<ctrl:VMFactoryUserControl x:Class="GenericProject.View.GenericView"
x:TypeArguments="vm:GenericViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:SomeProject.Controls;assembly=SomeProject.Controls"
xmlns:vm="clr-namespace:GenericProject.ViewModel">
<Grid>
<!-- Column Definitions -->
<!-- Row Definitions -->
<Label Grid.Row="0" Grid.Column="0" Content="Generic Id:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Generic Property:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding GenericProperty, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
</ctrl:VMFactoryUserControl>
GenericView.xaml.cs
public partial class GenericView : VMFactoryUserControl<GenericViewModel>
{
public GenericView()
{
InitializeComponent();
}
}
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private GenericViewModel _generic;
private readonly VMFactoryViewModel<GenericViewModel> _genericFactory;
public GenericViewModel Generic
{
get { return _generic; }
private set
{
if (_generic != value)
{
_generic = value;
base.OnPropertyChanged("Generic");
}
}
}
public VMFactoryViewModel<GenericViewModel> GenericFactory
{
get { return _genericFactory; }
}
private void OnGenericFactoryCreatedViewModel(object sender, CreatedViewModelEventArgs<GenericViewModel> e)
{
Generic = e.ViewModel;
}
public MainPageViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
_genericFactory = new VMFactoryViewModel<GenericViewModel>(_dialogService, x => new GenericViewModel(_dialogService, GetGeneric(x)));
_genericFactory.CreatedViewModel += OnGenericFactoryCreatedViewModel;
}
private Generic GetGeneric(string genericId)
{
// Return some Generic model.
}
}
MainPage.xaml
<Page x:Class="GenericProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:GenericProject.View">
<StackPanel>
<!-- Headers and Additional Content. -->
<vw:EventView DataContext="{Binding Generic}"
VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
</StackPanel>
</Page>

Upgrade from ListBox to CollectionViewSource not working

I already have a working ListBox with Items from my local database. Now I wanted to upgrade this to a CollectionViewSource for filtering. After my upgrade the new ListBox with CollectionViewSource shows nothing.
MainPage Code Behind:
// Data context for the local database
private BuildingDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<Building> _buildings;
public ObservableCollection<Building> BuildingTable
{
get
{
return _buildings;
}
set
{
if (_buildings != value)
{
_buildings = value;
NotifyPropertyChanged("BuildingTable");
}
}
}
public CollectionViewSource Source { get; set; }
// Konstruktor
public MainPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new BuildingDataContext(BuildingDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from Building todo in toDoDB.BuildingTable
select todo;
// Execute the query and place the results into a collection.
BuildingTable = new ObservableCollection<Building>(toDoItemsInDB);
Source = new CollectionViewSource();
Source.Source = BuildingTable;
// Call the base method.base.OnNavigatedTo(e);
}
For that purpose I added the lines:
public CollectionViewSource Source { get; set; }
Source = new CollectionViewSource();
Source.Source = BuildingTable;
I tried as well to put
Source = new CollectionViewSource();
Source.Source = BuildingTable;
in my MainPage Constructor. It doesnt work as well.
My Mainpage.xaml:
<!--<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding BuildingTable}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">-->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding Source.View}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Name="textBlockShortcut" Text="{Binding Shortcut}" Width="Auto" HorizontalAlignment="Left" Grid.Column="0" Margin="0,0,0,5" FontSize="36" />
<TextBlock Name="textBlockName" Text="{Binding BuildingName}" Width="Auto" HorizontalAlignment="Left" Grid.Column="1" Margin="0,0,0,5" FontSize="36" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The first commented line shows the old working listbox without CollectionViewSource. So what am I missing?
EDIT:
private void goToNavigation(object sender, RoutedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (toDoItemsListBox.SelectedIndex == -1)
return;
// Navigate to the new page
PhoneApplicationService.Current.State["SelectedItem"] = toDoItemsListBox.SelectedItem;
NavigationService.Navigate(new Uri("/NavigationPage.xaml", UriKind.Relative));
// Reset selected index to -1 (no selection)
toDoItemsListBox.SelectedIndex = -1;
}
You would usually create and bind to a CollectionViewSource in XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvs"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}" ...>
...
</ListBox>
</Grid>
and in code-behind just access that CollectionViewSource like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
...
var cvs = Resources["cvs"] as CollectionViewSource;
cvs.Source = BuildingTable;
}
You don't use the CollectionViewSource class directly, you use a CollectionView of the appropriate type.
View = CollectionViewSource.GetDefaultView( myCollection );
and then you bind that directly to your source.
ItemsSource="{Binding View}"
You can and only should use a CollectionViewSource from xaml, because thats its main purpose. From code you should directly create a CollectionView or use the GetDefaultView method.

Resources