I been following this Walkthrough which does a great job of explaining switching between two views, plus more.
What I'm trying to adapt the project to do is, instead of switching between two views, is show two views side by side.
Andy set up the following in his MainWindowViewModel placing ViewModels into an OC:
public class MainWindowViewModel : NotifyUIBase
{
public ObservableCollection<ViewVM> Views {get;set;}
public MainWindowViewModel()
{
ObservableCollection<ViewVM> views = new ObservableCollection<ViewVM>
{
new ViewVM{ ViewDisplay="Customers", ViewType = typeof(CustomersView), ViewModelType = typeof(CustomersViewModel)},
new ViewVM{ ViewDisplay="Products", ViewType = typeof(ProductsView), ViewModelType = typeof(ProductsViewModel)}
};
Views = views;
RaisePropertyChanged("Views");
views[0].NavigateExecute();
}
}
In MainWindow.xaml.cs navigation calls ShowUserControl() to set the view
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Apply default form level font style
Style = (Style)FindResource(typeof(Window));
Messenger.Default.Register<NavigateMessage>(this, (action) => ShowUserControl(action));
this.DataContext = new MainWindowViewModel();
}
private void ShowUserControl(NavigateMessage nm)
{
EditFrame.Content = nm.View;
}
}
My Code:
I won't be needing them in an OC and I won't be switching between views, they will be displayed at the same time side-by-side. So I was thinking what I need to do is
public class MainWindowViewModel : NotifyUIBase
{
private ViewVM m_MobileDeviceRequestsVM;
private ViewVM m_AuthorizedMobileDevicesVM;
public ViewVM MobileDeviceRequestsVM
{
get { return m_MobileDeviceRequestsVM; }
}
public ViewVM AuthorizedMobileDevicesVM
{
get { return m_AuthorizedMobileDevicesVM; }
}
public MainWindowViewModel()
{
m_MobileDeviceRequestsVM = new ViewVM { ViewDisplay = "MobileDeviceRequests", ViewType = typeof(MobileDeviceRequestsView), ViewModelType = typeof(MobileDeviceRequestsViewModel) };
m_AuthorizedMobileDevicesVM = new ViewVM { ViewDisplay = "AuthorizedMobileDevices", ViewType = typeof(AuthorizedMobileDevicesView), ViewModelType = typeof(AuthorizedMobileDevicesViewModel) };
}
}
The problem I'm facing is how to bind these ViewModel Views in to my grid, tried using a couple of ContentControl however that's not working.
How can I accomplish this?
<Window x:Class="MobileDeviceAuthenticator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MobileDeviceAuthenticator"
Title="Device Authorization" Height="381" Width="879">
<Grid>
<Grid Margin="0,25,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Authorized Devices" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Margin="20,10,0,0" VerticalAlignment="Top" />
<ContentControl Grid.Row="1" Grid.Column="0" Content="{Binding AuthorizedMobileDevicesVM.View}" />
<Label Content="Device Requests" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Margin="20,10,0,0" VerticalAlignment="Top" />
<ContentControl Grid.Row="1" Grid.Column="1" Content="{Binding MobileDeviceRequestsVM.View}" />
</Grid>
</Grid>
</Window>
I looked at the example's ViewVM class again after I made my comment regarding my reservations about the approach. Ignoring any of that and assuming you have not modified the example's ViewVM code below:
public class ViewVM
{
public string ViewDisplay { get; set; }
public Type ViewType { get; set; }
public Type ViewModelType { get; set; }
public UserControl View { get; set; }
public RelayCommand Navigate { get; set; }
public ViewVM()
{
Navigate = new RelayCommand(NavigateExecute);
}
public void NavigateExecute()
{
if(View == null && ViewType != null)
{
View = (UserControl)Activator.CreateInstance(ViewType);
}
var msg = new NavigateMessage { View = View, ViewModelType = ViewModelType, ViewType = ViewType };
Messenger.Default.Send<NavigateMessage>(msg);
}
}
The issue is that the View property is only assigned to via reflection when NavigateExecute is called. When you bind to AuthorizedMobileDevicesVM.View, it's not instantiated yet. You can move the reflection code into the constructor for your case and it'll work. Of course this means it'll increase memory usage of your application if you're using ViewVM elsewhere for page navigation - looks like it's by design meant to create the view only as necessary.
Related
I am trying to create my own expander control.
It has a header and a body. The header is a button that shows or hides the body.
This is all in an <ItemsControl/> that is bound to an observable collection - Each object in the observable collection creates its own expander control.
For the most part, this is working. What I am having trouble with is the expanding part. When clicking on the header, all the controls show or hide their contents at the same time - I know what the problem is: All the properties are bound to a value at the same time. How do I make them operate independently from one another?
I am not even sure that the way I am trying to handle this is the best way (I could use the expander control but I am still learning WPF and XAML and would like to recreate some standard controls for practice and understanding). I am still trying to figure out how bindings work; mostly RelativeSource and everything associated with it.
I would also like to pass a selected object as a parameter through the x:name="btn_body" button.
My current XAML:
<ItemsControl ItemsSource="{Binding testName}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="parent" Margin="50,5" Background="Yellow"
Height="{Binding DataContext.ParentHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<DockPanel x:Name="child_header" Height="50" DockPanel.Dock="Top" Background="Aqua">
<Button Height="50" x:Name="btn_header"
Command="{Binding DataContext.OpenCloseBoxCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text=" CONTENT "/>
</StackPanel>
</Button>
</DockPanel>
<DockPanel x:Name="child_body" DockPanel.Dock="Top"
Visibility="{Binding DataContext.Visable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}"
Height="{Binding DataContext.ChildHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button x:Name="btn_body" Grid.Column="2" Grid.Row="3" Grid.RowSpan="3" Margin="35,5" Content="CONFIRM"/>
</Grid>
</DockPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ViewModel for the above (DataContext is set in the MainWindow code behind)
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
string content = File.ReadAllText(#"C:\\tempfiles\\people.json");
var listOfPeople = JsonConvert.DeserializeObject<apiResponse>(content);
testName = new ObservableCollection<person>(listOfPeople.people);
OpenCloseBoxCommand = new RelayCommand(OpenClose);
}
public RelayCommand OpenCloseBoxCommand { get; set; }
public ObservableCollection<person> testName { get; set; }
private int _parentheight = 150;
public int ParentHeight
{
get { return _parentheight; }
set { _parentheight = value; }
}
private Visibility _visable = Visibility.Visible;
public Visibility Visable
{
get { return _visable; }
set { _visable = value; }
}
private int _childheight = 100;
public int ChildHeight
{
get { return _childheight; }
set { _childheight = value; }
}
public void OpenClose()
{
if(_parentheight == 150)
{
_parentheight = 50;
_visable = Visibility.Hidden;
OnPropertyChanged("ParentHeight");
} else
{
_parentheight = 150;
_visable = Visibility.Visible;
OnPropertyChanged("ParentHeight");
}
}
}
EDIT
I have moved the properties over to the person class, along with the command. The general functionality works as expected now, but, Is the following the correct way to do things?
I understand the idea behind the MVVM pattern and as such I am trying to keep the UI stuff away from everything else as what I am trying to achieve is purely UI logic and non critical to functionality (this expander could be replaced by a grid and everything would function normally).
It just seems that UI "logic" is creeping into where it shouldn't be.
public class person : ViewModelBase { //needed for OnPropertyChanged
public person()
{
OpenCloseBoxCommand = new RelayCommand(OpenClose);
}
public RelayCommand OpenCloseBoxCommand { get; set; }
public string PersonId { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
private int _parentheight = 150;
public int ParentHeight
{
get { return _parentheight; }
set { _parentheight = value; }
}
private Visibility _visable = Visibility.Visible;
public Visibility Visable
{
get { return _visable; }
set { _visable = value; }
}
private int _childheight = 100;
public int ChildHeight
{
get { return _childheight; }
set { _childheight = value; }
}
public void OpenClose()
{
if (_parentheight == 150)
{
_parentheight = 50;
_visable = Visibility.Hidden;
OnPropertyChanged("ParentHeight");
}
else
{
_parentheight = 150;
_visable = Visibility.Visible;
OnPropertyChanged("ParentHeight");
}
}
}
ParentHeight and Visable should be property of person,not MainWindowViewModel.
In addition, as a member of MainWindowViewModel,OpenCloseBoxCommand should be added a parameter for pass current item.
I want to know why do not you choose Expander control?
I ever implemennt a Expander in Listbox,it worked well.
I am working on an app that I had originally written all in C# (including UI) but now I am rewriting it so that UI is handled by xml. Every part of the app worked fine when it was all in C#, however, now that I have switched UI over to xml, I am getting an error in my ViewModels.
error: 'StartPageViewModel' does not contain a definition for 'Navigation' and no accessible extension method 'Navigation' accepting a first argument of type 'StartPageViewModel' could be found (are you missing a using directive or an assembly reference?)
XML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="SampleApp.Views.StartPage">
<ContentPage.Content>
<StackLayout>
<Grid VerticalOptions="CenterAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="OnePlayerGame" Text="One Player Game"
Command="{Binding StartOnePlayerGameCommand}"
Grid.Row="0" Grid.Column="0"
HorizontalOptions="Center"/>
<Button x:Name="TwoPlayerGame" Text="Two Player Game"
Command="{Binding StartTwoPlayerGameCommand}"
Grid.Row="0" Grid.Column="1" HorizontalOptions="Center" />
</Grid>
</StackLayout>
</ContentPage.Content>
Code behind
namespace SampleApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StartPage : ContentPage
{
public StartPage()
{
InitializeComponent();
BindingContext = new StartPageViewModel();
}
}
}
ViewModel code
namespace SampleApp.ViewModels
{
class StartPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Command StartOnePlayerGameCommand { get; set; }
public Command StartTwoPlayerGameCommand { get; set; }
public StartPageViewModel()
{
StartOnePlayerGameCommand = new Command(() => StartOnePlayerGame());
StartTwoPlayerGameCommand = new Command(() => StartTwoPlayerGame());
}
private void StartOnePlayerGame()
{
//the error is here (Navigation has a red squigly under it)
this.Navigation.PushAsync(new Views.OnePlayerPage());
}
private void StartTwoPlayerGame()
{
//the error is here (Navigation has a red squigly under it)
this.Navigation.PushAsync(new Views.NameEntryPage());
}
}
}
Navigation is a property of Page and you can pass the Navigation through the ViewModel Constructor, then use it in the ViewModel:
class StartPageViewModel : INotifyPropertyChanged
{
public INavigation myNavigation { get; set; }
public StartPageViewModel(INavigation navigation)
{
myNavigation = navigation;
StartOnePlayerGameCommand = new Command(() => StartOnePlayerGame());
StartTwoPlayerGameCommand = new Command(() => StartTwoPlayerGame());
}
private void StartOnePlayerGame()
{
//the error is here (Navigation has a red squigly under it)
myNavigation.PushAsync(new Views.OnePlayerPage());
}
private void StartTwoPlayerGame()
{
//the error is here (Navigation has a red squigly under it)
myNavigation.PushAsync(new Views.NameEntryPage());
}
}
Pass it from Page:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext = new StartPageViewModel(Navigation);
}
}
Another way it that you can try to use Application.Current.MainPage.Navigation:
private async void StartOnePlayerGame()
{
//the error is here (Navigation has a red squigly under it)
await Application.Current.MainPage.Navigation.PushAsync(new Views.OnePlayerPage());
}
I'd like to use a nested ObservableCollection in MVVM in order to add as many groups and users as possible. However, I don't know how to create/add a new user. I also don't know how to bind the new users to the XAML.
(NOTE: this time, I just need two persons.)
I'm new to C#, WPF and MVVM.
I'm learning MVVM referring to this site: https://riptutorial.com/wpf/example/992/basic-mvvm-example-using-wpf-and-csharp
I've been trying this since last week, but no luck.
I tried:
outerObservableCollection.Add(
new ObservableCollection<User>
{
{
FirstName = "Jane",
LastName = "June",
BirthDate = DateTime.Now.AddYears(-20)
}
}
);
... which ends up with the following error:
The name 'BirthDate' does not exist in the current context
(I guess the cause of this issue is that I didn't create a 'user' object, so 'user.BirthDate' is not accessible.)
Let me show the entire codes.
MainWindow.xaml:
<Window x:Class="MVVM_RIP_Tutorial.MainWindow"
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"
xmlns:local="clr-namespace:MVVM_RIP_Tutorial"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="32.8"/>
<RowDefinition Height="28.8"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="37*"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="155*"/>
</Grid.RowDefinitions>
<!--1st Person-->
<TextBlock Grid.Column="1" Grid.Row="0" Margin="320.6,4,398.6,3.2" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold" Width="0"/>
<Label Grid.Column="0" Grid.Row="1" Margin="0,4.8,4.4,2.8" Content="First Name:" HorizontalAlignment="Right" Width="70"/>
<!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. -->
<TextBox Grid.Column="1" Grid.Row="1" Margin="3.6,4.8,0,1.8" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="2" Margin="0,5.2,4.4,1.6" Content="Last Name:" HorizontalAlignment="Right" Width="69"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="3.6,5.2,0,1.6" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="3" Margin="0,6.4,4.4,4.2" Content="Age:" HorizontalAlignment="Right" Grid.RowSpan="2" Width="33"/>
<TextBlock Grid.Column="1" Grid.Row="3" Margin="3.6,6.4,0,10.2" Text="{Binding Age}" HorizontalAlignment="Left" Grid.RowSpan="2" Width="0"/>
<!--2nd Person-->
<!--<TextBlock Grid.Column="1" Grid.Row="4" Margin="320.6,4,398.6,3.2" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold" Width="0"/>
<Label Grid.Column="0" Grid.Row="5" Margin="0,4.8,4.4,2.8" Content="First Name:" HorizontalAlignment="Right" Width="70"/>
--><!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. --><!--
<TextBox Grid.Column="1" Grid.Row="5" Margin="3.6,4.8,0,1.8" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="6" Margin="0,5.2,4.4,1.6" Content="Last Name:" HorizontalAlignment="Right" Width="69"/>
<TextBox Grid.Column="1" Grid.Row="6" Margin="3.6,5.2,0,1.6" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>
<Label Grid.Column="0" Grid.Row="7" Margin="0,6.4,4.4,4.2" Content="Age:" HorizontalAlignment="Right" Grid.RowSpan="2" Width="33"/>
<TextBlock Grid.Column="1" Grid.Row="7" Margin="3.6,6.4,0,10.2" Text="{Binding Age}" HorizontalAlignment="Left" Grid.RowSpan="2" Width="0"/>-->
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace MVVM_RIP_Tutorial
{
public partial class MainWindow : Window
{
private readonly MyViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MyViewModel();
// The DataContext serves as the starting point of Binding Paths
DataContext = _viewModel;
}
}
}
User.cs:
using System;
namespace MVVM_RIP_Tutorial
{
sealed class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
}
MyViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace MVVM_RIP_Tutorial
{
// INotifyPropertyChanged notifies the View of property changes, so that Bindings are updated.
sealed class MyViewModel : INotifyPropertyChanged
{
private User user;
ObservableCollection<ObservableCollection<User>> outerObservableCollection
= new ObservableCollection<ObservableCollection<User>>();
//ObservableCollection<User> user = new ObservableCollection<User>();
public string FirstName
{
get { return user.FirstName; }
set
{
if (user.FirstName != value)
{
user.FirstName = value;
OnPropertyChange("FirstName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
public string LastName
{
get { return user.LastName; }
set
{
if (user.LastName != value)
{
user.LastName = value;
OnPropertyChange("LastName");
// If the first name has changed, the FullName property needs to be udpated as well.
OnPropertyChange("FullName");
}
}
}
// This property is an example of how model properties can be presented differently to the View.
// In this case, we transform the birth date to the user's age, which is read only.
public int Age
{
get
{
DateTime today = DateTime.Today;
int age = today.Year - user.BirthDate.Year;
if (user.BirthDate > today.AddYears(-age)) age--;
return age;
}
}
// This property is just for display purposes and is a composition of existing data.
public string FullName
{
get { return FirstName + " " + LastName; }
}
public MyViewModel()
{
user = new User
{
FirstName = "John",
LastName = "Doe",
BirthDate = DateTime.Now.AddYears(-30)
};
//outerObservableCollection.Add(user);
//outerObservableCollection.Add(
// new ObservableCollection<User>
// {
// {
// FirstName = "Jane",
// LastName = "June",
// BirthDate = DateTime.Now.AddYears(-20)
// }
// }
//);
);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
... Please help me. Thank you in advance.
First of all, welcome to C#, WPF, and MVVM!
From your description it sounds like you would like to display a tree of users within groups... with that in mind, you could implement something like this to accomplish that goal:
Models
public class GroupModel
{
public GroupModel(uint id, string name)
{
Id = id;
Name = name;
}
public uint Id { get; }
public string Name { get; }
}
public class UserModel
{
public UserModel(uint id, string firstName, string surname, DateTime dateOfBirth)
{
Id = id;
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
}
public uint Id { get; }
public string FirstName { get; }
public string Surname { get; }
public DateTime DateOfBirth { get; }
}
ViewModels
Base classes
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public abstract class ViewModelBase<TModel> : ViewModelBase
where TModel : class
{
private TModel _model;
public ViewModelBase(TModel model)
=> _model = model;
/*
* There is a design choice here to allow the model
* to be swapped at runtime instead or to treat the
* view model as immutable in which case the setter
* for the Model property should be removed.
*/
public TModel Model
{
get => _model;
set
{
if (ReferenceEquals(_model, value))
{
return;
}
_model = value;
OnPropertyChanged();
}
}
}
Concrete classes
public class GroupViewModel : ViewModelBase<GroupModel>
{
public GroupViewModel(GroupModel model)
: base(model)
{
}
public ObservableCollection<UserViewModel> Users { get; }
= new ObservableCollection<UserViewModel>();
public void AddUser(UserModel user)
{
var viewModel = new UserViewModel(user);
Users.Add(viewModel);
}
}
public class UserViewModel : ViewModelBase<UserModel>
{
public UserViewModel(UserModel model)
: base(model)
{
}
// convenience property; could be done completely in XAML as well
public string FullName => $"{Model.FirstName} {Model.Surname}";
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
// create sample user groups
for (var groupIndex = 1u; groupIndex <= 5u; ++groupIndex)
{
var groupName = $"Group {groupIndex}";
var groupModel = new GroupModel(groupIndex, groupName);
var groupViewModel = new GroupViewModel(groupModel);
UserGroups.Add(groupViewModel);
for (var userIndex = 1u; userIndex <= 5u; ++userIndex)
{
var userModel = new UserModel(
id: userIndex,
firstName: "John",
surname: $"Smith",
dateOfBirth: DateTime.Today);
groupViewModel.AddUser(userModel);
}
}
}
public ObservableCollection<GroupViewModel> UserGroups { get; }
= new ObservableCollection<GroupViewModel>();
}
View
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:UserGroups.ViewModels"
x:Class="UserGroups.Views.MainWindow"
Title="User Groups"
Width="1024"
Height="768">
<Window.DataContext>
<viewModels:MainViewModel />
</Window.DataContext>
<Grid>
<TreeView ItemsSource="{Binding Path=UserGroups}">
<TreeView.Resources>
<!-- Template for Groups -->
<HierarchicalDataTemplate DataType="{x:Type viewModels:GroupViewModel}"
ItemsSource="{Binding Path=Users}">
<TextBlock Text="{Binding Path=Model.Name}" />
</HierarchicalDataTemplate>
<!-- Template for Users -->
<DataTemplate DataType="{x:Type viewModels:UserViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Model.Id, StringFormat='[{0}]'}"
Margin="3,0" />
<TextBlock Text="{Binding Path=FullName}"
Margin="3,0" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Here's what you end up with:
There are lots of frameworks that take care of a lot of the tedious work of working with the MVVM pattern (e.g. removing most/all of the boilerplate code for INotifyPropertyChanged). Here are just a few to look at:
MVVM Light Toolkit
Prism
ReactiveUI
Some additional resources that might also be useful:
SnoopWPF
WPF Tutorial
It's not entirely clear to me what the result is supposed to look like, but here are some initial suggestions.
I wouldn't try nesting an Observable collection inside another one. Instead, define something like a separate Group model class that has a list of User objects as its field.
I take it the user is supposed to enter values for your bound properties in the xaml in order to create a new user? You need to add a button or something that the user can press after filling those values out. The button click should be bound to a RelayCommand (add MVVMLight to the project if necessary) in the view model. The method invoked by said relaycommand would instantiate a new User object using the fields bound in the xaml and add to your ObservableCollection.
<Button Command="{Binding Path=CreateUserCommand}">
<TextBlock Text="Create User"/>
</Button>
Then in the view model...
public RelayCommand CreateUserCommand { get; private set; }
CreateUserCommand = new RelayCommand(() =>
{
User user = new User
{
FirstName = FirstName,
LastName = LastName,
//...etc.
}
collectionOfUsers.Add(user);
});
"I also don't know how to bind the new users to the XAML."
So far I don't see any xaml code that would handle displaying new users. Seems to me you'd want to bind your collection of users to a grid or combo box. After the user enters new user properties in the textboxes and clicks the appropriate button, the grid or combo box would update. You could have separate controls for separate groups. Again, that part is not entirely clear to me.
Given (3), your ObservableCollection of users needs to be a property in the view model that implements OnPropertyChanged.
Hope that helps.
I'm trying to bind a reusable button in a carusel, what I want to achieve is a add lets say 6 buttons each button will have a command that according to button name will navigate to the proper page.
I can do that by doing this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<Button Command="{x:Bind ViewModel.NavigateToPage1, Mode=OneWay}"
Content="{x:Bind ViewModel.Name, Mode=OneWay}"/>
</toolkitcontrols:Carousel>
If I do that, I'll be adding 5 more buttons and i'll have to write properties for each button.
So instead I want to use a UserControl and just write something like this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="data:ButtonInfo">
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
But I've failed doing it, I found out some tutorial but all as I understand will make me write this like of code:
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
like 6 times, and i dont know how it will take x:DataType of DataTemplate for my list of properties.
This is my UserControl.xaml.cs
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public ButtonInfo ButtonInfo => (DataContext as ButtonInfo);
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
Loaded += NavigationMenuButtonTemplate_Loaded;
}
private void NavigationMenuButtonTemplate_Loaded(object sender, RoutedEventArgs e)
{
Bindings.Update();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
this is my ButtonInfo.cs
public class ButtonInfo
{
public string Symbol { get; set; }
public string FontFamily { get; set; }
public string MenuName { get; set; }
public string BenefitKind { get; set; }
public string Status { get; set; }
}
and this is my UserControl.xaml
<Button x:Name="NavigationMenuTemplate"
Width="300"
Height="300"
Command="{Binding NavigateToPageCommand, ElementName=root, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NavigationMenuIconTextBlock"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
FontFamily="{x:Bind ButtonInfo.FontFamily, Mode=OneWay, FallbackValue='Webdings'}"
Text="{x:Bind ButtonInfo.Symbol, Mode=OneWay, FallbackValue=''}"
FontSize="150"
Foreground="Black"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Text="{x:Bind ButtonInfo.MenuName, Mode=OneWay, FallbackValue='CALCULADORA JORNADAS EXTRAORDINARIAS'}"
FontSize="12"
Foreground="Black"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Grid.Row="2"
Grid.Column="0"
Text="{x:Bind ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Left"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Grid.Row="2"
Grid.Column="1"
Text="{x:Bind ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Right"/>
</Grid>
</Button>
can somebody help me and point me in the right direction please.
what am I missing?
The ItemTemplate approach in your question is actually on the right track.
In the end, your XAML would look something similar to the following(only a few properties are included but you get the idea) -
<toolkitcontrols:Carousel ItemsSource="{x:Bind ButtonInfoCollection}">
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="local:ButtonInfo">
<local:NavigationMenuButton NavigateToPageCommand="{Binding DataContext.NavigateToPageCommand, ElementName=MyPageName}"
NavigateToPageCommandParameter="{x:Bind PageType}"
MenuName="{x:Bind MenuName}"
SymbolPath="{x:Bind Symbol}" />
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
With the structure above in mind, you just need to expose these properties as dependency properties in your NavigationMenuButton user control. See below as a simple example -
NavigationMenuButton XAML
<UserControl x:Class="DesignTest.NavigationMenuButton">
<!--If any of the properties can be updated, change the binding Mode to OneWay-->
<Button Command="{x:Bind NavigateToPageCommand, Mode=OneWay}" CommandParameter="{x:Bind NavigateToPageCommandParameter}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image x:Name="SymbolImage" Stretch="UniformToFill" />
<TextBlock Text="{x:Bind MenuName, FallbackValue='JORNADAS EXTRAORDINARIAS', TargetNullValue='JORNADAS EXTRAORDINARIAS'}" Grid.Column="1" />
</Grid>
</Button>
</UserControl>
NavigationMenuButton Code-behind
public sealed partial class NavigationMenuButton : UserControl
{
public NavigationMenuButton()
{
InitializeComponent();
}
public ICommand NavigateToPageCommand
{
get => (ICommand)GetValue(NavigateToPageCommandProperty);
set => SetValue(NavigateToPageCommandProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandProperty = DependencyProperty.Register(
"NavigateToPageCommand", typeof(ICommand), typeof(NavigationMenuButton), new PropertyMetadata(null));
public object NavigateToPageCommandParameter
{
get => GetValue(NavigateToPageCommandParameterProperty);
set => SetValue(NavigateToPageCommandParameterProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandParameterProperty = DependencyProperty.Register(
"NavigateToPageCommandParameter", typeof(object), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string MenuName
{
get => (string)GetValue(MenuNameProperty);
set => SetValue(MenuNameProperty, value);
}
public static readonly DependencyProperty MenuNameProperty = DependencyProperty.Register(
"MenuName", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string SymbolPath
{
get => (string)GetValue(SymbolPathProperty);
set => SetValue(SymbolPathProperty, value);
}
public static readonly DependencyProperty SymbolPathProperty = DependencyProperty.Register(
"SymbolPath", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null, (s, e) =>
{
// We don't do the x:Bind for this property in XAML because the Image control's Source property
// doesn't accept a string but a BitmapImage, so one workaround is to do the conversion here.
var self = (NavigationMenuButton)s;
var image = self.SymbolImage;
var symbolPath = (string)e.NewValue;
image.Source = new BitmapImage(new Uri(self.BaseUri, symbolPath ?? "/Assets/default_path"));
}));
}
Note you will need to include a PageType property in your ButtonInfo class for navigation purpose.
public Type PageType { get; set; }
I personally don't like having a navigation command defined at the item level (i.e in the ButtonInfo class), instead, I use an ElementName binding in the Carousel's data template to search up a level and bind to the NavigateToPageCommand defined in the page's DataContext, which is the page's ViewModel.
This means that this ViewModel will have both ButtonInfoCollection and NavigateToPageCommand defined like below -
public ObservableCollection<ButtonInfo> ButtonInfoCollection { get; } = new ObservableCollection<ButtonInfo>
{
new ButtonInfo { MenuName = "New Menu", PageType = typeof(BlankPage1), Symbol = "/Assets/StoreLogo.png" }
};
public DelegateCommand<Type> NavigateToPageCommand { get; } = new DelegateCommand<Type>(type =>
App.Frame.Navigate(type));
I hope this all makes sense. Good luck!
Ok, Firstable to Dilmah its always posible to build a reusable usercontrol within any itemTemplate. And I'll show you how right now and right here.
I have come up with two solution the first solution its inspire in what I was looking for after reading a lot about databing {x:Bind} and {Binding} markup, I was able to learn how to create a reusable UserControlTemplate
SOLUTION No.1
First I'll show you how to create a Navigation menu with a carusel control, which can be found at nuget package Microsoft.Toolkit.Uwp.UI.Control
so this is my code now on my MainMenuPage reference to carusel control:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.NavMenuButtonVMs}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate>
<usercontrolvm:NavigationMenuButtonTemplate/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
this important part of this code is at ItemSource property which is x:Bind to my NavMenuButtonVms ObservableCollection, and my Usercontrol which is wrapped withing Carousel.ItemTemplate and DataTemplate tags which will allows us to be able to reuse our code and create a N number of controls within our list.
the next is my ViewModel for my MainMenuPage:
public class MainMenuPageViewModel : Mvvm.ViewModelBase
{
ObservableCollection<NavigationMenuButtonTemplateViewModel> _NavMenuButtonVMs = default(ObservableCollection<NavigationMenuButtonTemplateViewModel>);
public MainMenuPageViewModel()
{
Shell.HamburgerMenu.IsFullScreen = false;
NavMenuButtonVMs = GetNavMenuButtonInfo();
}
public string Title => GetLocalizeString("MainMenuPageViewModelTitle");
public ObservableCollection<NavigationMenuButtonTemplateViewModel> NavMenuButtonVMs
{
get { return _NavMenuButtonVMs; }
private set { Set(ref _NavMenuButtonVMs, value); }
}
public override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
NavigationService.ClearHistory();
return base.OnNavigatedToAsync(parameter, mode, state);
}
}
As you can see I initialize my ObservableCollection within my constructor.
The method GetNavMenuButton() is a static class in a Helpers Namespace but i'll show you the code so you can have an idea of how to seed the list also you can notice that I'm not calling the static class that because i'm using C# 6.0 syntax where you can call directly static methods within your class.
you can add a using statement for static classes like this one:
using static Ceneam.Helpers.NavigationMenuButtonViewModelHelper;
this statement allows you to use a static method like this:
GetNavMenuButtonInfo();
instead of this:
NavigationMenuButtonViewModelHelper.GetNavMenuButtonInfo();
I explained this in case you dont understand my code.
then I'll create my usercontrol which I'll show you the xaml, xaml.cs and also the viewmodel.
pay attention to the binding markup at usercontrol since you'll have to quit using x:Bind within a reusable usercontrol.
This is my NavigationMenuButtonTemplate.xaml
<UserControl
x:Class="Ceneam.UserControlViews.NavigationMenuButtonTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Ceneam.UserControlViews"
xmlns:vm="using:Ceneam.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="400"
d:DesignWidth="400">
<Grid VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="NavigationMenuTemplate"
Command="{Binding NavigateToPageCommand, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="NavigationMenuIconImage"
Source="{Binding ButtonInfo.Symbol, Mode=OneWay, FallbackValue='ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png'}"
PointerEntered="NavigationMenuIconImage_PointerEntered"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Text="{Binding ButtonInfo.MenuName, Mode=OneWay, FallbackValue='JORNADAS EXTRAORDINARIAS'}"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Text="{Binding ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Text="{Binding ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"/>
</Grid>
</Button>
</Grid>
as you can see I'm using binding markup only and the reason is because I use a viewmodel with a parameter that right i had to create a dependency on my usercontrol:
public class NavigationMenuButtonTemplateViewModel : Mvvm.ViewModelBase
{
ButtonInfo _ButtonInfo = default(ButtonInfo);
public NavigationMenuButtonTemplateViewModel() { }
public NavigationMenuButtonTemplateViewModel(ButtonInfo buttonInfo)
{
ButtonInfo = new ButtonInfo
{
BenefitKind = buttonInfo.BenefitKind,
Status = buttonInfo.Status,
MenuName = buttonInfo.MenuName,
Symbol = buttonInfo.Symbol
};
}
public ButtonInfo ButtonInfo
{
get { return _ButtonInfo; }
set { Set(ref _ButtonInfo, value); }
}
public DelegateCommand NavigateToPageCommand => new DelegateCommand(async () => { await ExecuteNavigateToPageCommand(); });
private async Task ExecuteNavigateToPageCommand()
{
var message = new MessageDialog("Test");
await message.ShowAsync();
}
}
since you create a contructor with a parameter in the viewmodel I wasnt able to create a strongly type bind with that constructor that is the main reason that made me leave behind x:bind markup for my usercontrol that means that you cant use x:bind methods on events. you will have to use styling methods within the xaml.cs file of your usercontrol.
If you declare in you xaml something like this:
<UserControl.DataContext>
<vm:usercontrol x:Name=ViewModel/>
<UserControl.DataContext>
it will always trigger your parameterless constructor getting rid of your init values of even worse getting NullReferenceExceptions, also you could use DataContext for DesignTime Data but it has to be declare at the beginning of your file and I wont center on it here.
and finally at my static class is where I create my UC (usercontrol) with a parameter in them this is my static class:
public static class NavigationMenuButtonViewModelHelper
{
public static ObservableCollection<NavigationMenuButtonTemplateViewModel> GetNavMenuButtonInfo()
{
var data = new ObservableCollection<NavigationMenuButtonTemplateViewModel>();
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/SatSunBonusWhite256x256.png",
MenuName = "PRIMAS SABATINAS Y DOMINICALES",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png",
MenuName = "JORNADAS EXTRAORDINARIAS",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/VacationBonusWhite256x256.png",
MenuName = "PRIMA VACACIONAL",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PecoWhite256x256.png",
MenuName = "PERMISOS ECONOMICOS",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PunctualityBonusWhite256x256.png",
MenuName = "INCENTIVO PUNTUALIDAD Y ASISTENCIA",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/BonForSeniorityWhite256x256.png",
MenuName = "BONO DE ANTIGUEDAD",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/WageIncreaseWhite256x256.png",
MenuName = "RETROACTIVO SUELDO",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
return data;
}
private static void AddNavMenuButtonItem(ObservableCollection<NavigationMenuButtonTemplateViewModel> data, NavigationMenuButtonTemplateViewModel item)
{
data.Add(item);
}
}
also if you wish to style properties programatically you should do it at xaml.cs like for example like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
private void NavigationMenuIconImage_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var image = (Image)sender;
var bitmapImage = image.Source as BitmapImage;
var uri = bitmapImage?.UriSource;
var uriPath = uri?.AbsolutePath;
var newUriPath = $#"ms-appx://{uriPath.Replace("White", "Black")}";
image.Source = new BitmapImage(new Uri(newUriPath, UriKind.RelativeOrAbsolute));
}
}
**SOLUTION No.2: **
another solution could be using usercontrols with dependency properties like this one:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemSource="{x:Bind ViewModel.MenuList}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<usercontrolvm:NavigationMenuButtonTemplate ButtonInfo="{x:Bind ViewModel.MenuList[0],Mode=OneWay}"
NavigateToPageCommand = "{x:Bind ViewModel.NavigateToPageCommand}"/>
You will have to create a NavigationMenuButtonTemplate.xaml.cs with dependency properties like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
public ButtonInfo ButtonInfo
{
get { return (ButtonInfo)GetValue(ButtonInfoProperty); }
set { SetValue(ButtonInfoProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonInfoProperty =
DependencyProperty.Register("ButtonInfo",
typeof(ButtonInfo),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
I dont like this solution because I'll have to repeat code at xaml file but its a good choice too.
Hope you like my answer I think it can be used by many of us and its appliable to many other controls with your endless imagination.
I've been working on this problem for about 3 hours now, and I got to a dead end.
Currently I'm trying to bind a list to a ComboBox.
I have used several methods to bind the List:
Code behind:
public partial class MainWindow : Window
{
public coImportReader ir { get; set; }
public MainWindow()
{
ir = new coImportReader();
InitializeComponent();
}
private void PremadeSearchPoints(coSearchPoint sp)
{
//SearchRefPoint.DataContext = ir.SearchPointCollection;
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = Name;
The data was binded correctly but the DisplayMemeberPath for some reason returned the name of the class and not the name of it's member.
The XAML method returned an empty string...
<ComboBox x:Name="SearchRefPoint" Height="30" Width="324" Margin="0,10,0,0"
VerticalAlignment="Top" ItemsSource="{Binding ir.SearchPointCollection}"
DisplayMemberPath="Name">
I've also tried to fill it with a new list which I create in the MainWindow. the result was the same in both cases.
Also I've tried to create and ListCollectionView which was success, but the problem was that I could get the index of the ComboBox item. I prefer to work by an Id. For that reason I was looking for a new solution which I found at: http://zamjad.wordpress.com/2012/08/15/multi-columns-combo-box/
The problem with this example is that is not clear how the itemsource is being binded.
Edit:
To sum things up: I'm currently trying to bind a list(SearchPointsCollection) of objects(coSearchPoints) defined in a class (coImportReader).
namespace Import_Rates_Manager
{
public partial class MainWindow : Window
{
public coImportReader ir;
public coViewerControles vc;
public coSearchPoint sp;
public MainWindow()
{
InitializeComponent();
ir = new coImportReader();
vc = new coViewerControles();
sp = new coSearchPoint();
SearchRefPoint.DataContext = ir;
}
}
}
//in function....
SearchRefPoint.ItemsSource = ir.SearchPointCollection;
SearchRefPoint.DisplayMemberPath = "Name";
namespace Import_Rates_Manager
{
public class coImportReader
{
public List<coSearchPoint> SearchPointCollection = new List<coSearchPoint>();
}
}
namespace Import_Rates_Manager
{
public class coSearchPoint
{
public coSearchPoint()
{
string Name = "";
Guid Id = Guid.NewGuid();
IRange FoundCell = null;
}
}
}
This results in a filled combobox with no text
Here a simple example using the MVVM Pattern
XAML
<Window x:Class="Binding_a_List_to_a_ComboBox.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">
<Grid HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ComboBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding SearchPointCollection , UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Id}" Grid.Row="0"/>
<TextBlock Text="{Binding Name}" Grid.Row="1"/>
<TextBlock Text="{Binding Otherstuff}" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Bind NOW" Grid.Column="0" Grid.Row="1" Click="Button_Click"/>
<TextBlock Text="{Binding MySelectedIndex, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Grid Grid.Column="1" Grid.Row="1"
DataContext="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Id}" Grid.Column="0"/>
<TextBlock Text="{Binding Name}" Grid.Column="1"/>
<TextBlock Text="{Binding SomeValue}" Grid.Column="2"/>
</Grid>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using Import_Rates_Manager;
namespace Binding_a_List_to_a_ComboBox
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DataContext = new coImportReader();
}
}
}
namespace Import_Rates_Manager
{
public class coImportReader : INotifyPropertyChanged
{
private List<coSearchPoint> myItemsSource;
private int mySelectedIndex;
private coSearchPoint mySelectedItem;
public List<coSearchPoint> SearchPointCollection
{
get { return myItemsSource; }
set
{
myItemsSource = value;
OnPropertyChanged("SearchPointCollection ");
}
}
public int MySelectedIndex
{
get { return mySelectedIndex; }
set
{
mySelectedIndex = value;
OnPropertyChanged("MySelectedIndex");
}
}
public coSearchPoint MySelectedItem
{
get { return mySelectedItem; }
set { mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
#region cTor
public coImportReader()
{
myItemsSource = new List<coSearchPoint>();
myItemsSource.Add(new coSearchPoint { Name = "Name1" });
myItemsSource.Add(new coSearchPoint { Name = "Name2" });
myItemsSource.Add(new coSearchPoint { Name = "Name3" });
myItemsSource.Add(new coSearchPoint { Name = "Name4" });
myItemsSource.Add(new coSearchPoint { Name = "Name5" });
}
#endregion
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class coSearchPoint
{
public Guid Id { get; set; }
public String Name { get; set; }
public IRange FoundCell { get; set; }
public coSearchPoint()
{
Name = "";
Id = Guid.NewGuid();
FoundCell = null;
}
}
public interface IRange
{
string SomeValue { get; }
}
}
Here are 3 Classes:
MainWindow which set VM as his Datacontext
coImportReader the Class which presents your properties for your bindings
coSearchPoint which is just a Container for your information
IRange which is just an Interface
The Collection you are binding to needs to be a property of ir not a field.
Also try this :
public coImportReader ir { get; set; }
public <type of SearchPointCollection> irCollection { get { return ir != null ? ir.SearchPointCollection : null; } }
Bind to irCollection and see what errors you get if any.
The DisplayMemberPath should contain the property name of the elements in your collection. Assuming the elements in the SearchPointCollection are of the type SearchPoint and this class has a Property SearchPointName you should set DisplayMemberPath like this:
SearchRefPoint.DisplayMemberPath = "SearchPointName";
Edit:
In your code the class coSearchPoint has the Field Name defined in the Constructor. Name has to be a Property of the class, otherwise the Binding can't work.