Application
x:Class="sqlasynctest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:sqlasynctest"
xmlns:viewModel="using:sqlasynctest.ViewModel"
RequestedTheme="Light">
<Application.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" />
</ResourceDictionary>
</Application.Resources>
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (!SimpleIoc.Default.IsRegistered<IDal>())
{
SimpleIoc.Default.Register<IDal, Dal>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
MainViewModel
public class MainViewModel : ViewModelBase
{
private readonly IDal _iDal;
private IList<User> _user;
public IList<User> Users
{
get { return _user; }
set { Set("Users", ref _user, value); }
}
public MainViewModel(IDal dal)
{
_iDal = dal;
LoadDataCommand = new RelayCommand(async () => await LoadData());
}
public ICommand LoadDataCommand { get; private set; }
public async Task LoadData()
{
Users = await _iDal.LoadAllUser();
}
}
MainPage
<Page
x:Class="sqlasynctest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:sqlasynctest"
DataContext="{Binding Main, Source={StaticResource Locator}}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.Row="1" SelectionMode="Single"
ItemsSource="{Binding Users}" Margin="120,0,0,40">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Table And Methods are fine but i think binding is wrong , help me
LoadData()
Method is not called any where .
I got a solution
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var viewModel = (MainViewModel)DataContext;
if (viewModel != null)
{
viewModel.LoadData();
}
I don't like to touch MainPage.cs
any other way to do this in xaml code
You have LoadDataCommand in your view model, but it is not called from anywhere. There is no magic here – you have to invoke it either from XAML, or from code-behind file. Here's the way this can be done in XAML:
Install Microsoft.Xaml.Behaviors.Uwp.Managed NuGet package.
Modify XAML of your main page:
<Page
...
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:c="using:Microsoft.Xaml.Interactions.Core">
<i:Interaction.Behaviors>
<c:EventTriggerBehavior EventName="Loaded">
<c:InvokeCommandAction Command="{Binding LoadDataCommand}"/>
</c:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Grid ...>
Remove overridden OnNavigatedTo method from the code-behind file.
LoadDataCommand will now be invoked each time MainPage is loaded.
Your problems is that your collection is a List. It does not notify the UI about chnages. User an ObservableCollection instead and it will start to work.
Related
I am still new to WPF and MVVM and am trying to keep the seperation between View and View Model.
i have an app, essentially a projects task list app, in this i create projects and within each project i can create a set of tasks. Most is working well, but essentially i cannot get a command binding on a checkbox in a user control to work using DP, inherited datacontext etc. i always ge a binding failed error when running the app. i am trying to bing to a command in the viewmodel of the view which contains the user controls.
i created a user control to pull the task data together in the view, the command is on the checkbox
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding SetComplete}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
in the user control code behind i have set the DP and the set text function is working
namespace TaskProjectApp.Controls
{
/// <summary>
/// Interaction logic for TaskControl.xaml
/// </summary>
public partial class TaskControl : UserControl
{
public UserTask Task
{
get { return (UserTask)GetValue(TaskProperty); }
set { SetValue(TaskProperty, value); }
}
// Using a DependencyProperty as the backing store for Task. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TaskProperty =
DependencyProperty.Register("Task", typeof(UserTask), typeof(TaskControl), new PropertyMetadata(new UserTask()
{
Title = "title",
Description = "none",
Comments = "none"
}, SetText));
private static void SetText(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TaskControl task = d as TaskControl;
if (task != null)
{
task.titleTB.Text = (e.NewValue as UserTask).Title;
task.DescriptionTB.Text = (e.NewValue as UserTask).Description;
task.priority.Text = (e.NewValue as UserTask).Priority.ToString();
task.iscomplete.IsChecked = (e.NewValue as UserTask).IsComplete;
}
}
public TaskControl()
{
InitializeComponent();
}
}
}
now to make this work i set the binding of the user control in the window as so, the listview takes the usercontrols and implements the observable collection of tasks.
<Window x:Class="TaskProjectApp.Views.ProjectsView"
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:TaskProjectApp.Views"
xmlns:uc="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
Title="ProjectsView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<uc:ProjectControl Project="{Binding UserProject}" />
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
</Grid>
</Window>
this seems to completely stop me using the command, i set the datacontext in the code behind, to the whole window
public partial class ProjectsView : Window
{
public ProjectViewModel ProjectViewModel { get; set; }
public ProjectsView()
{
InitializeComponent();
}
public ProjectsView(UserProject userProject)
{
InitializeComponent();
ProjectViewModel = new ProjectViewModel(userProject);
DataContext = ProjectViewModel;
}
}
and reading trying to solve this has shown that the usercontrol should inherit the datacontext of the parent window.
i have seen solutions using relative paths and DPs for the commands as well as people saying these are not needed just let the inherited datacontext handle it.
but i have tried all three an neither works.
the interface shows me a message box saying no datacontext found, although i notice this is the case when you set the datacontext in code behind and not the xaml.
the SetCommand is created in the projects view model and its a property not a field as i have seen this fail for that reason too.
namespace TaskProjectApp.ViewModels
{
public class ProjectViewModel
{
public UserProject UserProject { get; set; }
public ProjectViewModel(UserProject userProject)
{
UserProject = userProject;
Tasks = new ObservableCollection<UserTask>();
NewProjectTask = new NewProjectTaskCommand(this);
DeleteProjectTask = new DeleteProjectTaskCommand(this);
SetComplete = new SetCompleteCommand();
ReadTaskDatabase();
}
public ObservableCollection<UserTask> Tasks { get; set; }
public NewProjectTaskCommand NewProjectTask { get; set; }
public DeleteProjectTaskCommand DeleteProjectTask { get; set; }
public SetCompleteCommand SetComplete { get; set; }
public UserTask SelectedTask { get; set; }
public void ReadTaskDatabase()
{
List<UserTask> list = new List<UserTask>();
using (SQLiteConnection newConnection = new SQLiteConnection(App.databasePath))
{
newConnection.CreateTable<UserTask>();
list = newConnection.Table<UserTask>().ToList().OrderBy(c => c.Title).ToList();
}
Tasks.Clear();
foreach (UserTask ut in list)
{
if (ut.ProjectId == UserProject.Id)
{
Tasks.Add(ut);
}
}
}
}
}
if anyone can point out where i am going wrong tat will be great as i fear i am now not seeing the wood for the trees.
I found the solution thanks to Ash link Binding to Window.DataContext.ViewModelCommand inside a ItemsControl not sure how i missed it, maybe wrong key words. anyway because the datacontext of the usercontrol is being made into my data class in the observable list Tasks
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
you need to use a relative path inside the user control to look up past the ItemTemplate to the ListView itself as this uses the viewmodel data context to bind to, so has access to the right level
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding DataContext.SetComplete, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
this might be limiting in future as it measn the usercontrol will look for a listview to bind the command, but it solves the immediate issue.
I bind a MainWindowViewModel to the DataContext of a MainWindow.
Then I initialize this MainWindowViewModel to a specific itemsPageViewModel.
The problem is that on startUp I see itemsPageViewModel 's class name instead of its content:
Startup
However, after switching pages through buttons (RelayCommands), the same ViewModel now shows its content:
PageSwitched
Both operations pass through the same code-line:
CurrentPageViewModel = _itemsPageViewModel
How can it produce different results?
CODE
MainWindow.xaml
<Window x:Class="ListItemUI.Views.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"
mc:Ignorable="d"
Title="ListItemUI" Height="400" Width="600">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Content="ITEMS" Margin="2" Command ="{Binding SelectItemsPageViewModel}"></Button>
<Button Content="HELP" Margin="2" Command ="{Binding SelectInfoPageViewModel}"></Button>
</StackPanel>
</Grid>
<ContentControl Grid.Row="2" Content="{Binding CurrentPageViewModel}"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using ListItemUI.InfoPage.ViewModels;
using ListItemUI.ListItemPage.ViewModels;
using ListItemUI.ViewModels;
namespace ListItemUI.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow(IPageFactory itemPageFactory, IPageFactory infoPageFactory)
{
InitializeComponent();
var mainWindowVM = new MainWindowViewModel(itemPageFactory,infoPageFactory);
DataContext = mainWindowVM;
}
}
}
MainWindowViewModel.cs
using System;
using System.Windows.Input;
using ListItemUI.ListItemPage.ViewModels;
namespace ListItemUI.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private readonly IListItemUIViewModel _itemsPageViewModel;
private readonly IListItemUIViewModel _infoPageViewModel;
public ICommand SelectItemsPageViewModel { get; }
public ICommand SelectInfoPageViewModel { get; }
public object CurrentPageViewModel
{
get { return _currentPageViewModel; }
set
{
_currentPageViewModel = value;
RaisePropertyChanged(() => CurrentPageViewModel);
}
}
private object _currentPageViewModel;
public MainWindowViewModel(IPageFactory itemsPageFactory, IPageFactory infoPageFactory)
{
_itemsPageViewModel = itemsPageFactory.CreatePage();
_infoPageViewModel = infoPageFactory.CreatePage();
SelectItemsPageViewModel = new RelayCommand(_ =>
{
CurrentPageViewModel = _itemsPageViewModel;
});
SelectInfoPageViewModel = new RelayCommand(_ =>
{
CurrentPageViewModel = _infoPageViewModel;
});
CurrentPageViewModel = _itemsPageViewModel;
}
}
}
ListItemPage.xaml (dataTemplates)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels ="clr-namespace:ListItemUI.ListItemPage.ViewModels">
<DataTemplate DataType="{x:Type viewModels:ItemViewModel}">
<StackPanel>
<TextBlock Foreground="RoyalBlue" FontWeight="Bold" Text="{Binding Path=ItemViewDescription, StringFormat='Group Info = {0}'}"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ItemsPageViewModel}">
<StackPanel>
<TextBlock Text ="{Binding Path=Title}"></TextBlock>
<Grid Grid.Column="0" Background="Aquamarine">
<ListBox ItemsSource="{Binding Path=LocalItemViewModels}" Margin="5">
</ListBox>
</Grid>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
App.xaml
<Application x:Class="ListItemUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ListItemPage/Views/ListItemPage.xaml"></ResourceDictionary>
<ResourceDictionary Source="InfoPage/Views/InfoView.xaml"></ResourceDictionary>
<!--GLOBAL RESOURCES -->
<ResourceDictionary Source="Views/GlobalResources.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
We used a workaround to solve this specific problem.
We chose not to use resource dictionaries, putting the dataTemplates of the viewModels directly into the mainwindow.xaml: now everything works.
Something strange happens when we use resource dictionaries.
I have tried data binding in WPF.
But it is showing few errors.Please help me.
I am attaching the code.I have create a simple text block and tried to bind the string. Also I want to know how Windows.datacontext works? In my code it is giving an error. please help me out.
Xaml code
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Window.DataContext>
<l:DataBinding />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
</Window>
**Code behind**
namespace Shweta
{
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
Setupviewmodel();
}
private void Setupviewmodel
{
TextString="this worked";
}
public string TextString{get;set;}
}
}
Okay so first of all read the error messages ... It clearly says that l is not defined in XAML but still you're trying to use it : <l:DataBinding />...
Fix this by declaring l in your XAML :
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:l="<your l declaration"/>
Another thing is that you haven't implemented INotifyPropertyChanged so you'r value wont get updated anyway.
Implement this like such :
public partial class DataBinding : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string text;
public string TextString
{
get { return text; }
set { text = value; NotifyPropertyChanged(); }
}
public DataBinding()
: base()
{
InitializeComponent();
Setupviewmodel();
// as #Nahuel Ianni stated, he has to set DataContext to CodeBehind
// in order to be able to get bindings work
DataContext = this; // <-- only if not binded before
}
public void Setupviewmodel() // forgot to to place ()
// produced error : `A get or set accessor expected`
{
TextString = "this worked";
}
}
Yet another thing is that you have to specify DataContext only when it's not the same as your code behind so you do not need this part :
<Window.DataContext>
<l:DataBinding />
</Window.DataContext>
You are not specifying the DataContext correctly as you are trying to set it up on XAML by using a namespace that has not been declared. For more info on XAML namespaces, check the following link.
In your example it would be on the xaml side:
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
</Window>
And in your code behind:
namespace Shweta
{
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
this.DataContext = this; // Pay attention to this line!
Setupviewmodel();
}
private void Setupviewmodel()
{
TextString="this worked";
}
public string TextString{get;set;}
}
}
The difference with the original version is that I'm not specifying the DataContext on XAML but on the code behind itself.
The DataContext can be considered as the place where the view will retrieve the information from. When in doubt, please refer to this MSDN article or you could learn about the MVVM pattern which is the pillar of working with XAML.
In order to make this work you have to set the DataContext properly. I'd suggest to create a viewmodel class and bind to that. Also I initialized the binding in the codebehind because your namespaces are missing. You can do that in xaml aswell. For now to give you something to work with try this for your codebehind:
public partial class DataBinding : Window
{
public DataBinding()
{
InitializeComponent();
DataContext = new DataBindingViewModel();
}
}
public class DataBindingViewModel
{
public DataBindingViewModel()
{
Setupviewmodel();
}
private void Setupviewmodel()
{
TextString = "this worked";
}
public string TextString { get; set; }
}
And change your view to this:
<Window x:Class="Shweta.DataBinding"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBinding" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54*" />
<ColumnDefinition Width="224*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="59*" />
<RowDefinition Height="202*" />
</Grid.RowDefinitions>
<Grid Grid.Column="1" Grid.Row="1">
<TextBlock Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="{Binding TextString, TargetNullValue=Test}" VerticalAlignment="Top" Width="68" />
</Grid>
</Grid>
Note that the Text property will only be set at initialization. If you want DataBinding at runtime your DataBindingViewModel will have to implememnt INPC and throw the PropertyChanged Event after setting the property bound to.
I am new to WPF with mostly Winforms and Webforms experience. I am trying to learn WPF and one thing that I am trying to learn is creating beautiful UI in XAML. I have been trying to replicate the UI of StaffLynx application. The screen shots are present here
http://nextver.com/site/portfolio/stafflynx/
I cannot figure out in WPF, what will be the best way to create the placeholder container for the windows. In the link above you can see all the pages (views) are loaded in a custom shaped window. How can I create a re-usable window like this?
Should I just override the template of some control?
In short I am not sure what is the right way to create a custom shaped window such as the one used by StaffLynx app.
Please advise.
Maybe you should try using a ContentTemplateSelector. Here's a good example..
Here's a simple example that I made that may fit to your scenario. I have a window that has a border and inside the border is a ContentControl that has a template selector that will allow you to choose which view to display.
Here's the view:
Take a look at the local:MyContentTemplateSelector tag.
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="FirstTemplate">
<TextBlock Text="First" />
</DataTemplate>
<DataTemplate x:Key="SecondTemplate">
<TextBlock Text="Second" />
</DataTemplate>
<local:MyContentTemplateSelector FirstTemplate="{StaticResource FirstTemplate}" SecondTemplate="{StaticResource SecondTemplate}"
x:Key="mytemplateSelector" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border BorderThickness="1" BorderBrush="Red" Grid.Row="0">
<ContentControl ContentTemplateSelector="{StaticResource mytemplateSelector}" Content="{Binding SelectedViewModel}"/>
</Border>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1">
<Button Command="{Binding SelectFirstViewModel}">Go to First Template</Button>
<Button Command="{Binding SelectSecondViewModel}">Go to Second Template</Button>
</StackPanel>
</Grid>
</Window>
Here's the view model:
public class MainVm : ViewModelBase
{
private FirstVm _FirstViewModel;
public FirstVm FirstViewModel
{
get { return _FirstViewModel; }
set { Set(ref _FirstViewModel, value); }
}
private SecondVm _SecondViewModel;
public SecondVm SecondViewModel
{
get { return _SecondViewModel; }
set { Set(ref _SecondViewModel, value); }
}
private ViewModelBase _SelectedViewModel;
public ViewModelBase SelectedViewModel
{
get { return _SelectedViewModel; }
set { Set(ref _SelectedViewModel, value); }
}
public ICommand SelectFirstViewModel
{
get
{
return new RelayCommand(() => { this.SelectedViewModel = FirstViewModel; });
}
}
public ICommand SelectSecondViewModel
{
get
{
return new RelayCommand(() => { this.SelectedViewModel = SecondViewModel; });
}
}
public MainVm()
{
FirstViewModel = new FirstVm();
SecondViewModel = new SecondVm();
SelectedViewModel = this.FirstViewModel;
}
}
These can be any view model that you have for your pages:
public class FirstVm : ViewModelBase
{
}
public class SecondVm : ViewModelBase
{
}
And here's the template selector. This is the the important part. Whenever you change the content of you ContenControl, in this case the content is bound to the SelectedViewmodel property of the MainVm, the SelectTemplate method in this class will be called. that's where you put the logic on which view or data template display.
public class MyContentTemplateSelector : DataTemplateSelector
{
public DataTemplate FirstTemplate { get; set; }
public DataTemplate SecondTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is FirstVm)
return FirstTemplate;
if (item is SecondVm)
return SecondTemplate;
return null;
}
}
It will look like something like these:
Oh, ok if you just want one of many examples how to do that sort of thing. Here's a quick example of how to cut a corner like that using Clip, give it a shot. Hope it helps.
<Window x:Class="NestedCutCornerWindowCWSO"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NestedCutCornerWindowCWSO" Height="500" Width="800">
<Grid Height="350" Width="500">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Fill="Navy"
Clip="M0,0 L485,0 500,15 500,100 0,100 z"/>
<TextBlock Foreground="White" FontSize="20" Text="Something" Margin="5"/>
<Rectangle Grid.Row="1"
Fill="White"
Stroke="Navy" StrokeThickness="2"/>
<TextBlock Grid.Row="1" Foreground="Black" FontSize="30"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Some Other Stuff..."/>
</Grid>
</Window>
I am currently working within a WPF user control using MVVM. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window.Resources>
<ObjectDataProvider x:Key="TabsList" ObjectType="{x:Type local:MainWindowModel}" MethodName="GetTabs"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Source={StaticResource TabsList}}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding Source={StaticResource TabsList}, Path=MyUserControl}"/>
</Grid>
Data provider class is as below
public class MainWindowModel
{
public List<TabInfo> GetTabs()
{
return new List<TabInfo>()
{
new TabInfo() { TabName="Tab1", MyUserControl = new UserControl1()},
new TabInfo() { TabName="Tab2", MyUserControl = new UserControl2()}
};
}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControl MyUserControl { get; set; }
}
And now I have two usercontrols UserControl1 and UserControl2 each has a text box control. I would like to update the Text property of the textbox control in the UserControl1 whenever the Text property of the Textbox control in the UserControl2 is updated. To do this, I tried as below.
UserControl1
<UserControl x:Class="MoreOnBinding2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20" Text="{Binding Path=UserControl1Text}"/>
</Grid>
UserControl1ViewModel
public class UserControl1VM : ViewModelBase
{
public UserControl1VM()
{
this.UserControl1Text = "10";
}
private string userControl1Text;
public string UserControl1Text
{
get { return userControl1Text; }
set
{
if (userControl1Text != value)
{
userControl1Text = value;
RaisePropertyChanged(() => UserControl1Text);
}
}
}
}
UserControl2
<UserControl x:Class="MoreOnBinding2.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MoreOnBinding2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}},
UpdateSourceTrigger=PropertyChanged, Path=UserControl1Text}" />
</Grid>
But it is not working. It seems there is a problem in the RelativeSource tag in the UserControl2. Can someone help on this.
You could use a Dependency Property on your user controls this would give you a bind able property. see link
EDIT 1
Just had another look at your class setup. If you are following the MVVM approach I don't think you should have a reference to your user control in your view model
View Model should be more Like
public Class
{
ObservableCollection<TabInfo> _Tabs = new ObservableCollection<TabInfo>();
public ObservableCollection<TabInfo> Tabs
{
get{return _Tabs;}
set {_Tabs = value;}//Need to implement INotifyPropertyChanged [Link][2]
}
public TabInfo SelectedTab {get;set;}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControlViewModel MyUserControlViewModel{ get; set; }
}
Then in your View
<Window.DataContext>
<vm:MainWindowModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding SelectedTab}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedTab.MyUserControlViewModel"/>
</Grid>
This should give you a list of Tabs and then when one is selected it will set the content of the ContentControl to the MyUserControlViewModel.
You would then need to use some sort of template selector to control the ContentTemplate to load different UserControls into the ContentControl
I haven't tested the code works and you would need to implement the INotifyPropertyChanged on all the public properties so the bindings update as the properties value is changed.
Hope that helps a bit.