Silverlight XAML: Referencing Code-behind class - silverlight

Assuming the following view model definition:
public class MyObject {
public string Name { get; set; }
}
public interface IMyViewModel {
ICommand MyCommand { get; }
IList<MyObject> MyList { get; }
}
And a UserControl with the following code behind:
public class MyView : UserControl {
public IMyViewModel Model { get; }
}
If my XAML looked like this:
<UserControl>
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<TextBlock Text="{Binding Name}" />
<Button Content="Execute My Command" cmd:Click.Command="{Binding Path=MyCommand, ?????????}" cmd:Click.CommandParameter="{Binding}" />
</ListBox.ItemTemplate>
</ListBox>
How can I bind my Button to the ICommand property of my code-behind class?
I'm using Prism and SL 3.0 and I need to bind each button in my list box to the same command on my view model.
Before my UserControl had a specific name and I was able to use the ElementName binding, but now my UserControl is used multiple times in the same parent view so I can't use that technique anymore and I can't figure out how to do this in XAML.
If it is my only option I can do it manually in the code-behind, but I'd rather do it declaratively in the XAML, if possible.

You need a DataContextProxy for this to work because you're no longer in the context of the UserControl. You've moved out of that and there is no good way to reach back into that context without something like the DataContextProxy. I've used it for my projects and it works great.

Related

Trouble binding an object with sub-objects

I'm using Xamarin.Forms (C#) and am attempting an MVVM approach.
My classes:
public class Parent
{
public string FirstName { get; set; }
public Child Children { get; set; }
public Parent GetOneParent() {...}
}
public class Child
{
public string FavoriteFruit { get; set; }
}
First of all, what is this type of class called having a "compound" property (i.e. a collection of children)? I don't know what this is referred to so I am limited in "Googling" it.
Ok, I create a single Parent object:
Parent OneParent = new Parent.GetOneParent();
Now I'd like to show in my XAML code:
parent name (in a label)
a list of children's favorite fruits (in a listview since there are multiple)
What's the binding syntax for a label and then a listview for this type of object? {Binding ???}
The answer turned out to be rather simple... the binding context has to be specific to the page and then also more deeply, its controls.
So I did something like this...
In code-behind, I set the binding context:
BindingContext = Parent;
Then in the XAML code...
<StackLayout>
<Label Text="{Binding FirstName}" />
</StackLayout>
<StackLayout>
<!-- Now specify a deeper within the Parent object -->
<ListView x:Name="ParticipantList"
ItemsSource="{Binding Participants}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FavoriteFruit}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope that helps someone down the road!

wpf binding instantiated object to datacontext

EDIT : Question was not clear enough. In fact there are two of them.
Q1 :
I have a UserControl "CustomView" that is dynamically created with a template:
<Window.Resources>
<DataTemplate DataType="{x:Type my:CustomViewModel}">
<my:CustomView/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Path=CustomList}"/>
Where CustomList is a Property of type ObservableCollection<'CustomViewModel> belonging to MainWindowViewModel, which is the Window's DataContext.
In CustomView's Xaml code, there are some Properties binded to CustomViewModel's Properties. Everything works properly. But when I try to do this in CustomView's code behind :
public CustomView()
{
InitializeComponents();
if (this.DataContext == null) Console.WriteLine ("DataContext is null");
else Console.WriteLine(this.DataContext.GetType().ToString());
}
It is written in Console : 'DataContext is null', even if bindings are working betweeen CustomView and CustomViewModel. Do you know why it's working?
Q2 :
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question):
<UserControl x:Class="MyView.CustomView"
...
<UserControl.Resources>
<DataTemplate DataType="{x:Type myPicker:IndexPickerViewModel}">
<myPicker:IndexPicker/>
</DataTemplate>
<myPicker:IndexPickerViewModel x:Key="pickerViewModel" Index="{Binding Path=Id}/>
</Window.Resources/>
<ContentControl Content={StaticResource pickerViewModel}/>
What I have tried : I tried to make "IndexPickerViewModel" inherit from "DependencyObject" and make "Index" a DependencyProperty. But the following error message shows up :
"System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Id; DataItem=null; target element is 'IndexPickerViewModel' (HashCode=59604175); target property is 'Index' (type 'Nullable`1')
I believe this is because of what I asked just above. But is it possible to do something like that? If yes, what am I missing? And : Is this a stupid idea?
Thank you in advance for any help.
Now, imagine that CustomView has another UserControl (IndexPicker) inside of it. IndexPicker has an associated ViewModel too (IndexPickerViewModel) who's in charge with data access. I need to bind one property ("Index") of this IndexPickerViewModel to the previous CustomViewModel's property "Id". I want to instantiate it in StaticResources and bind it to the CustomViewModel (which I believe is the dataContext according to my previous question)
If IndexPicker doesn't have an explicitly set datacontext then IndexPicker will inherit the datacontext from it's parent element.
However if IndexPicker does already have a datacontext then you will have to use relative source binding with an ancestor search:
Index="{Binding Id, RelaticeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, FallbackValue={x:Null}}"
Of course you can probably already sense that this is messy. Going after standard properties of a UIElement or Control is quite safe (and common), but when you start going after custom properties then you are introducing dependencies between the child control and its parent (when the child control shouldn't know much of anything about its parent), and you are also bound to start getting binding errors at some stage (hence the use of a fallback value).
It seems that I've asked too early because I've found answers by myself.
Answer to Question1
When you have a UserControl that is dynamically created from a DataTemplate in which it is associated with another object (belonging to a ViewModel or to a Resource), this object is defined as the DataContext of the UserControl. However, you cannot reach it in the UserControl's constructor, you have to wait until the "Loaded" event is raised :
public CustomUserControl()
{
InitializeComponent();
Console.WriteLine(this.DataContext.ToString());
// This doesn't work : DataContext is null
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
Console.WriteLine(this.DataContext.ToString());
// or
Console.WriteLine((sender as UserControl).DataContext.ToString());
// this is Ok.
}
Answer to Question2
This is how you do to get a UserControl whose ViewModel is instantiated in a parent UserControl.Resources :
You don't do it.
Instead, you instantiate its ViewModel in its parent ViewModel. Full example :
MainWindow.xaml:
<Window x:Class="MainWindow"
...
xmlns:local="clr-namespace:my_project_namespace"
xmlns:cust="clr-namespace:CustomUserControl;assembly=CustomUserControl"
...>
<Window.Resources>
<DataTemplate DataType="{x:Type cust:CustomControlViewModel}">
<cust:CustomControlView>
</DataTemplate>
<!-- Here are listed all the types inheriting from CustomControlViewModel and CustomControlView.-->
<!-- CustomControlViewModel and CustomControlView are used as "abstract" classes-->
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Path=CustomVMList}"/>
</Grid>
</Window>
MainWindowViewModel.cs:
namespace my_project_namespace
{
public class MainWindowViewModel
{
public ObservableCollection<CustomControlViewModel> CustomVMList { get; set; }
public MainWindowViewModel()
{
CustomVMList = new ObservableCollection<CustomControlViewModel>();
// Fill in the list...
}
}
}
CustomControlView.xaml
<UserControl x:class="CustomUserControl.CustomControlView"
...
xmlns:my="clr-namespace:IndexPicker;assembly=IndexPicker"
...>
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:IndexPickerViewModel}">
<my:IndexPickerView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}/>
<ContentControl Content="{Binding Path=MyIndexPicker}"/>
</Grid>
</UserControl>
And this is where it's interesting :
CustomControlViewModel.cs:
namespace CustomUserControl
{
public class CustomControlViewModel : INotifyPropertyChanged
{
public IndexPickerViewModel MyIndexPicker{ get; set; }
public string Name { get ; set; }
public int Id
{
get
{
return MyIndexPicker.Index;
}
set
{
if (value != MyIndexPicker.Index)
{
MyIndexPicker.Index = value;
NotifyPropertyChanged("Id");
}
}
}
public CustomControlViewModel(string _name)
{
Name = _name;
MyIndexPicker = new IndexPickerViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
}
IndexPickerView.xaml:
<UserControl x:Class="IndexPicker.IndexPickerView"
...
...>
<Grid>
<Combobox ItemsSource="{Binding Path=MyTable}"
DisplayMemberPath="ColumnXYZ"
SelectedItem={Binding Path=SelectedRow}/>
</Grid>
</UserControl>
Finally
IndexPickerViewModel.cs:
namespace IndexPicker
{
public class IndexPickerViewModel : INotifyPropertyChanged
{
private DataAccess data;
public DataView MyTable { get; set; }
private DataRowView selectedRow;
public DataRowView SelectedRow
{
get { return selectedRow; }
set
{
selectedRow = value;
NotifyPropertyChanged("SelectedRow");
}
}
public int? Index
{
get
{
if (SelectedRow != null) return (int?)selectedRow.Row["Column_Id"];
else return null;
}
set
{
SelectedRow = MyTable[MyTable.Find((int)value)];
NotifyPropertyChanged("Index");
}
}
public IndexPickerViewModel()
{
data = new DataAccess();
MyTable = data.GetTableView("tableName");
MyTable.Sort = "Column_Id";
}
// And don't forget INotifyPropertyChanged implementation
}
}
This configuration is used with several different UserControls inheriting from CustomControlView and their ViewModel inheriting from CustomControlViewModel. They are dynamically created and listed in CustomVMList. Here CustomControlViewModel containing an IndexPicker is already a specialization.
Concrete use: Generic Dialog for CRUD database Tables, which can dynamically create UserControls depending on each Table Columns. The specialization shown here is used in case of a column containing a foreign key.
I hope its clear.
The code listed above may contain mistakes. Criticisms and remarks are welcome.

How to reuse a wpf view?

Is there a way to reuse a WPF View using it with Caliburn.Micro?
In example I've got a DetailViewBase.xaml with a SaveAndClose and Cancel button. Now I want to use the DetailViewModelBase and the XAML in PersonView(Model).
I hope this is question is clear.
If my assumption is correct, I think what you mean is that you want to 'compose' a more complex view from several simpler viewmodels. You can inherit from viewmodels, but obviously a viewmodel can only have one active view at a time.
However, using bindings you can compose multiple viewmodels and have them all render their respective views together to make a more complex UI
e.g.
A VM with save/cancel buttons
public class ActionsViewModel
{
public void Save()
{
}
public void Cancel()
{
}
}
The corresponding view:
<UserControl>
<StackPanel Orientation="Horizontal">
<Button x:Name="Save">Save</Button>
<Button x:Name="Cancel">Cancel</Button>
</StackPanel>
</UserControl>
Another view which composes itself and the ActionsViewModel
public class ComposedViewModel
{
public ActionsViewModel ActionsView
{
get; set;
}
public ComposedViewModel()
{
ActionsView = new ActionsViewModel();
}
public void DoSomething()
{
}
}
The View:
<UserControl>
<StackPanel Orientation="Horizontal">
<TextBlock>Hello World</TextBlock>
<Button x:Name="DoSomething">Do Something</Button>
<ContentControl x:Name="ActionsView" />
</StackPanel>
</UserControl>
When you use the convention binding (or use the attached properties) to bind a ContentControl CM will automatically bind the VM to the DataContext and wire up the view. This way you can compose multiple VMs into a single more complex piece of functionality.
Additionally, because of the action message bubbling - if you were to remove the 'Ok' and 'Cancel' implementation on the ActionsViewModel and put them in the ComposedViewModel implementation, the action message will bubble up to the ComposedViewModel instance and fire the methods on there instead
e.g.
public class ActionsViewModel
{
// Remove the command handlers
}
public class ComposedViewModel
{
public ActionsViewModel ActionsView
{
get; set;
}
public ComposedViewModel()
{
ActionsView = new ActionsViewModel();
}
public void DoSomething()
{
}
// CM will bubble the messages up so that these methods handle them when the user clicks on the buttons in the ActionsView
public void Save()
{
}
public void Cancel()
{
}
}
EDIT:
Sorry I forgot about this - convention based bindings won't allow messages to bubble, but you can just use the Message.Attach attached property for this to work:
// Make sure you include the caliburn namespace:
<UserControl xmlns:cal="http://www.caliburnproject.org">
<StackPanel Orientation="Horizontal">
<Button x:Name="Save" cal:Message.Attach="Save">Save</Button>
<Button x:Name="Cancel" cal:Message.Attach="Cancel">Cancel</Button>
</StackPanel>
</UserControl>
You can bind the ViewModel to the View explicitly using code like this:
cal:Bind.Model={Binding DetailViewModelBase}

How to use CollectionViewSource with design time data in Expression Blend?

I wonder how I can show design time data in Expression Blend that is located inside a SampleData.xaml using a CollectionViewSource? Before changing my code to use the CVS, I used an ObservableCollection. I was in the need to filter and sort the items inside there, thus I changed the code to use the CVS. Now my designer complains about not being able to fill the SampleData's NextItems with a proper structure to show up in Expression Blend. Here is some code I use inside the app:
MainViewModel.cs
class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
NextItems = new CollectionViewSource();
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
some functions to fill, filter, sort etc...
}
MainView.xaml:
<phone:PhoneApplicationPage
... some other stuff ...
d:DesignWidth="480"
d:DesignHeight="728"
d:DataContext="{d:DesignData SampleData/SampleData.xaml}">
<Grid
x:Name="LayoutRoot"
Background="Transparent">
<controls:Panorama>
<controls:PanoramaItem>
<ListBox ItemsSource="{Binding NextItems.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}" />
<StackPanel>
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>
SampleData.xaml
<local:MainViewModel
xmlns:local="clr-namespace:MyAppNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:swd="clr-namespace:System.Windows.Data;assembly=System.Windows" >
<local:MainViewModel.AllItems>
<local:ItemModel
FullName="Dummy"
Image="/Images/dummy.png" />
</local:MainViewModel.AllItems>
<local:MainViewModel.NextItems>
How to fill the CollectionViewSource's Source?
</local:MainViewModel.NextItems>
</local:MainViewModel>
So the question I can't find an answer to is how to fill the Source for NextItems in SampleDate.xaml? Any help would be much appreciated.
if you want to show sample data in the designer I would recommend you to do it from code. There are two ways of generating sample data for the Blend Designer or the VStudio designer:
From an XML file as you do.
From a c# class -> Best option
best option.
In WPF, in windows 8 and in WP7.5 and highger, you can access a propertie called:Windows.ApplicationModel.DesignMode.DesignModeEnabled making use of it you can seed your ObservableCollection from your view model:
public class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
if (DesignMode.DesignModeEnabled)
{
AllItems = FakeDataProvider.FakeDataItems;
}
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
}
In this way, if you change the model, you dont' have to regenerate an XML file, it's a little bit cleaner from a C# file. The FakeDataProvider is an static class where all design-time fake data are stored. So in you XAML the only thing you have to do is to bind your Listbox to the collection of your ViewModel.

MVVM DataTemplate Binding Issue

Perhaps this is a case of too much cold medicine, but I just can't seem to get this Binding correct.
Here is the (simplified) Window, with the a DataTemplate for each ViewModel type, which should just show an associated View:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:DefaultViewViewModel">
<local:DefaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:AnotherViewModel">
<other:AnotherView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Window>
Here is some of the MainViewModel (the actual ShowABCView methods are Command functions that do more than is shown here, for brevity):
class MainViewModel : ViewModelBase
{
private Stack<ViewModelBase> mContentViewStack;
public MainViewModel()
{
mContentViewStack = new Stack<ViewModelBase>();
ShowDefaultView();
}
public ViewModelBase CurrentViewModel
{
get { return mContentViewStack.Peek(); }
}
private ShowDefaultView()
{
DefaultViewViewModel viewModel = new DefaultViewViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
private ShowAnotherView()
{
AnotherViewModel viewModel = new AnotherViewModel();
mContentViewStack.Push(viewModel);
NotifyPropertyChanged("CurrentViewModel");
}
}
And the MainWindow startup code:
public MainWindow()
{
this.DataContext = new MainViewModel();
}
When I run this, I get the error
System.Windows.Data.Error: 40:
BindingExpression path error:
'Content' property not found on
'object' 'DefaultViewViewModel'
I know I'm missing something obvious here, but the Nyquil and friends betray me...
*EDIT - DefaultViewViewModel and DefaultView *
DefaultViewViewModel:
// ViewModelBase is basically just a wrapper for INotifyPropertyChanged,
// plus some other common-to-my-project properties
// (NOT INCLUDING A Content PROPERTY)
class DefaultViewViewModel : ViewModelBase
{
public DefaultViewViewModel() : base()
{
}
}
DefaultView:
<UserControl ...>
<TextBlock Text="Some Hard Coded Text Formatted To My Liking" />
</UserControl>
Well you haven't shown the code for the DefaultViewViewModel yet
but my guess is you defined "Content" as a field and not as a property.
to make sure that it will fix it, go ahead and overkill it by making Content a dependency property
hope that helps
Found the answer upstream from where I was looking. There was an incorrect binding (used regular Binding without RelativeSource of the TemplatedParent) in the base View control that all of our Views use.
No more Nyquil for me...

Resources