This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Binding to custom control inside DataTemplate for ItemsControl
(1 answer)
How to pass data from MainWindow to a User Control that's inside the MainWindow?
(1 answer)
Closed 3 years ago.
I am learning WPF for work and working on a simple Todo List application, and have run into some problems with the Observable List not updating the view.
I am using the MVVM Light framework, and have done a test subscription to the CollectionChanged event which does get fired when I add elements. I am doing multiple views, so in the off chance that I am not in the main UI thread I've added the MVVM Light's DispatcherHelper to ensure it is being invoked on the main thread. I've also tried manually re-triggering the INotifyPropertyChanged by using MVVM Light's RaisePropertyChanged function on the observable list (see the commented out code). To ensure it was not me screwing up the XAML I also changed the ListBox to a DataGrid with the same results.
Here are a list of some of the stack overflow messages I tried and tutorials I've followed trying to get this done:
Observable Collection Not Updating View
Observablecollection not updating list, when an item gets added
WPF MVVM observable collection not updating GUI
Binding to an Observable Collection
Using Mvvm Light
Todo Model Class
namespace WPFTodoList.Models
{
class TodoItem : ObservableObject
{
public String Title { get { return this._title; } set
{
Set(() => this.Title, ref this._title, value);
}
}
public String Description { get { return this._description; } set
{
Set(() => this.Description, ref this._description, value);
}
}
public int Priority { get { return this._priority; } set
{
Set(() => this.Priority, ref this._priority, value);
}
}
public bool Done { get { return this._done; } set
{
Set(() => this._done, ref this._done, value);
}
}
private String _title;
private String _description;
private int _priority;
private bool _done;
}
}
View Model Class
class TodoListAppViewModel : ViewModelBase
{
public ObservableCollection<TodoItem> Todos { get; private set; }
public RelayCommand AddTodoItemCommand { get{ return this._addTodoItemCommand ?? this._BuildAddTodoItemCommand(); } }
private RelayCommand _addTodoItemCommand;
private NavigationService _navService;
private TodoService _todoService;
private IDisposable _addTodoSubscription;
public TodoListAppViewModel(NavigationService nav, TodoService todo)
{
this._navService = nav;
this._todoService = todo;
this.InitViewModel();
}
public TodoListAppViewModel()
{
this._navService = NavigationService.GetInstance();
this._todoService = TodoService.GetInstance();
this.InitViewModel();
}
private void InitViewModel()
{
this.Todos = new ObservableCollection<TodoItem>();
this.Todos.CollectionChanged += this.CollectionChanged;
this._addTodoSubscription = this._todoService.AddTodoItem.Subscribe((value) =>
{
/*this.Todos.Add(value);
RaisePropertyChanged(() => this.Todos);*/
DispatcherHelper.CheckBeginInvokeOnUI(() => this.Todos.Add(value));
});
}
private RelayCommand _BuildAddTodoItemCommand()
{
this._addTodoItemCommand = new RelayCommand( () => this._navService.NavigateTo("addEdit"));
return this._addTodoItemCommand;
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine(e.Action.ToString());
}
~TodoListAppViewModel()
{
this._addTodoSubscription.Dispose();
}
}
}
View XAML
<UserControl x:Class="WPFTodoList.Views.TodoListAppView"
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:WPFTodoList.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate x:Key="TodoItemTemplate">
<Label Content="{Binding Title}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*" MaxHeight="180px"/>
<RowDefinition Height="16*"/>
</Grid.RowDefinitions>
<StackPanel Margin="24,-20,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Panel.ZIndex="5">
<Label Content="Your" Foreground="White" FontSize="16" />
<Label Content="Todo List" Foreground="White" FontSize="24" Margin="0,-15,0,0" />
</StackPanel>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5">
<Rectangle.Fill>
<RadialGradientBrush GradientOrigin="0.2,0.1" RadiusX="1" RadiusY="1" SpreadMethod="Reflect" MappingMode="RelativeToBoundingBox" Center="0.1,0.1">
<GradientStop Color="#FF5B447C" Offset="0.329"/>
<GradientStop Color="#FF1D1F5A" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Fill="#3FFFFFFF" VerticalAlignment="Bottom" Height="40" Panel.ZIndex="5"/>
<StackPanel Panel.ZIndex="10" Height="40" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<Button Background="Transparent" BorderBrush="Transparent" HorizontalAlignment="Right" Command="{Binding AddTodoItemCommand}" BorderThickness="0,0,0,0" SnapsToDevicePixels="True" >
<Button.ToolTip>
<Label Content="Add A Todo Item"/>
</Button.ToolTip>
<Image Source="../Resources/add.png" HorizontalAlignment="Right" />
</Button>
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding Todos}" ItemTemplate="{DynamicResource TodoItemTemplate}"/>
</Grid>
</UserControl>
View Code Back
public partial class TodoListAppView : UserControl
{
public TodoListAppView()
{
InitializeComponent();
TodoListAppViewModel vm = new TodoListAppViewModel();
this.DataContext = vm;
}
}
To simulate the todo without another 4-6 files I used a RX.net subject to relay the todo item from the other view to the list view (I can post code if requested).
As for how the window displays the views here is the root xaml
<Window x:Class="WPFTodoList.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:WPFTodoList"
mc:Ignorable="d"
xmlns:views="clr-namespace:WPFTodoList.Views"
xmlns:viewModels="clr-namespace:WPFTodoList.ViewModels"
Title="MainWindow" Height="600" Width="300">
<Window.Resources>
<DataTemplate x:Name="AddEditPage" DataType="{x:Type viewModels:AddEditTodoViewModel}">
<views:AddEditTodoView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="TodoListApp" DataType="{x:Type viewModels:TodoListAppViewModel}">
<views:TodoListAppView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding ActivePage}"/>
</Grid>
</Window>
ActivePage is then bound to an instance of TodoListAppViewModel
The expected behavior is when the subscription to the TodoService.AddTodoItem subscription is fired, the provided instance of a TodoListItem passed to the lambda should be added to the observable Todos list. The list in turn should update the view. Right now I am seeing the CollectionChanged event being fired inside of TodoListAppViewModel, however the view does not update.
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'm trying to learn MVVM pattern using WPF C#. And I'm running into an error when trying to close an opened window after saving information to an sqlite database. When the command to save a new contact is raised, I am getting an error on HasAddedContact(this, new EventArgs());
Error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
My ViewModel:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs code behind:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
Adding additional code where I call the new window:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
You're creating NewContactWindow's viewmodel in the constructor, correctly assigning it to DataContext, and correctly adding a handler to that event. Unfortunately, you also create a second instance of the same viewmodel in resources, and you manually set the Source property of all the bindings to use the one in the resources, which doesn't have the event handler.
Window.DataContext, which you set in the constructor, is the default Source for any binding in the Window XAML. Just let it do its thing. I also removed all the redundant Mode=TwoWay things from the Bindings to TextBox.Text, since that property is defined so that all bindings on it will be TwoWay by default. I don't think UpdateSourceTrigger=PropertyChanged is doing anything necessary or helpful either: That causes the Binding to update your viewmodel property every time a key is pressed, instead of just when the TextBox loses focus. But I don't think you're doing anything with the properties where that would matter; there's no validation or anything. But TextBox.Text is one of the very few places where that's actually used, so I left it in.
You should remove the analagous viewmodel resource in your other window. It's not doing any harm, but it's useless at best. At worst, it's an attractive nuisance. Kill it with fire and bury the ashes under a lonely crossroads at midnight.
<Window x:Class="Contacts_App.View.NewContactWindow"
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:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
This is the scenario: In a UserControl there is TabControl, which loads different views, and a button. Like this image:
Scenario
Button "Save" only can be enabled if fields "Name" and "Owner" are not empty. These fields are in a child view loaded in ItemTab.
This is the XAML (only with 1 TabItem to simplify)
<UserControl
x:Class="Winvet.Desktop.Views.VCliente.DatosCliente"
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"
xmlns:viewModels="clr-namespace:Winvet.Desktop.ViewModels.VMCliente"
xmlns:views="clr-namespace:Winvet.Desktop.Views.VCliente">
<Grid Margin="10 5 10 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<TabControl Grid.Column="0" Name="TabDatosCliente" ItemsSource="{Binding ItemsTabDatosCliente}" SelectedIndex="0">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModels:DatosClienteGeneralViewModel}">
<views:DatosClienteGeneral/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button VerticalAlignment="Bottom" Command="{ I want to bind this }">Guardar</Button>
</StackPanel>
</Grid>
</UserControl>
And this is the ViewModel (only 1 TabItem to simplify)
using System.Collections.ObjectModel;
using Winvet.Desktop.Common;
namespace Winvet.Desktop.ViewModels.VMCliente
{
public class DatosClienteViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> ItemsTabDatosCliente { get; private set; }
public DatosClienteViewModel()
{
ItemsTabDatosCliente = new ObservableCollection<ViewModelBase>
{
new DatosClienteGeneralViewModel()
};
}
}
}
I wan't to create a Command which checks if those two child view fields are not empty and enables button. How can I do it?
Routed commands burrow and bubble through the entire interface, so as long as you are in the same visual branch as the item raising the event, then you can handle it anywhere.
so in your VIEW
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CommandBindings.Add(new CommandBinding(command, execute, canExecute));
}
private void canExecute(object sender, CanExecuteRoutedEventArgs e)
{
throw new NotImplementedException();
//at this point you can pass it to your ViewModel
}
private void execute(object sender, ExecutedRoutedEventArgs e)
{
throw new NotImplementedException();
//at this point you can pass it to your ViewModel
}
}
where command is the RoutedCommand that is set on your buttons Command Property
I'm struggling with update issue. I have tab control with the listbox binded to an Observable Collection
ListBox HorizontalAlignment="Center" Height="450" VerticalAlignment="Top" Width="250"
x:Name="LbxMenu" Background="{x:Null}" BorderBrush="{x:Null}"
ItemsSource="{Binding TestListsNames}" FontFamily="Segoe UI Semilight" FontSize="18"/>
view model:
private ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
set{ _testListsName = value; }
}
After inserting entity to database there is an event which invokes TestListInitialize method in my ViewModel which should refresh collection and it works as I can see it in debugger. But listbox doesn't refresh and I have to restart application to see changes.
It worked great when it was in separate window but when I changed ui to tab control it doesn't.
Update function:
private void TestListNamesInitialize()
{
TestListsNames = db.GetTestListNamesFromDatabase();
if (TestListsNames.Count != 0) CanLoad = true;
}
Initial Window:
<Controls:MetroWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test.View.InitialWindow"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:tabdata="clr-namespace:Test.View.TabItems"
Title="Testownik" Height="600" Width="900" ShowTitleBar="True" ResizeMode="NoResize" Icon="../GraphicResources/Icon.ico">
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<Button Content="settings" />
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="4 0 0 0"
VerticalAlignment="Center"
Text="about" />
</StackPanel>
</Button>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Controls:MetroAnimatedTabControl x:Name ="MainTabControl">
<TabItem Header="Learn" Width="280">
<tabdata:LearnTabItem/>
</TabItem>
<TabItem Header="Database" Width="280">
<tabdata:DatabaseTabItem/>
</TabItem>
<TabItem Header="Statistics" Width="299">
<tabdata:StatisticsTabItem/>
</TabItem>
</Controls:MetroAnimatedTabControl>
Code behind:
public partial class InitialWindow : MetroWindow
{
InitialWindowViewModel viewModel=new InitialWindowViewModel();
public InitialWindow()
{
InitializeComponent();
DataContext = viewModel;
}
}
}
DatabaseTabItem:
<UserControl x:Class="Test.View.TabItems.DatabaseTabItem"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:tabData="clr-namespace:Test.View.TabItems"
Height="500" Width="900" Background="White" BorderBrush="Transparent">
<UserControl.Resources>
</UserControl.Resources>
<Grid>
<Controls:MetroAnimatedTabControl x:Name ="DatabaseTabControl" Grid.Column="0" TabStripPlacement="Left" >
<TabItem Header="Choose" Width="250" >
<tabData:ChooseFromDbTabItem/>
</TabItem>
<TabItem Header="Add" Width="250">
<tabData:AddToDbTabItem/>
</TabItem>
<TabItem Header="Remove" Width="250">
<tabData:DeleteFromDbTabItem/>
</TabItem>
</Controls:MetroAnimatedTabControl>
</Grid>
code behind:
DatabaseViewModel vm = new DatabaseViewModel();
public DatabaseTabItem()
{
InitializeComponent();
DataContext = vm;
}
}
ChooseFromDbTabItem:
<UserControl x:Class="Test.View.TabItems.ChooseFromDbTabItem"
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:Test.View.TabItems"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="650" Background="White" BorderBrush="Transparent">
<Grid>
<ListBox HorizontalAlignment="Center" Height="450" VerticalAlignment="Top" Width="250"
x:Name="LbxMenu" Background="{x:Null}" BorderBrush="{x:Null}"
ItemsSource="{Binding TestListsNames}" FontFamily="Segoe UI Semilight" FontSize="18"/>
</Grid>
code behind:
public partial class ChooseFromDbTabItem : UserControl
{
public ChooseFromDbTabItem()
{
InitializeComponent();
}
}
You have to rise PropertyChanged event for the reason you change your entire collection and not a single item (if you changed a single item that was updated through the Observable).
private ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
set
{
if (_testListsName != value)
{
_testListsName = value;
NotifyPropertyChanged("TestListsNames");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
You are not raising the PropertyChanged event when replacing the list using the property setter. Generelly, try to make collection properties readonly to reduce the risk for these sort of errors. Instead, clear the list and repopulate it. This will make sure that the view is notified about any changes.
public class ViewModel
{
private readonly ObservableCollection<string> _testListsName;
public ObservableCollection<string> TestListsNames
{
get { return _testListsName; }
}
private void TestListNamesInitialize()
{
_testListsName.Clear();
foreach(string name in db.GetTestListNamesFromDatabase())
{
_testListsName.Add(name);
}
if (_testListsNames.Count != 0) CanLoad = true;
}
}
However, note that this will raise changed events on each item using the .Add() call. See here: Can I somehow temporarily disable WPF data binding changes?
Edit: from your updated code. It can also be seen that you do not set the DataContext on your ChooseFromDbTabItem. You need to bind the DataContext property to the view model that exposes the collection:
<TabItem Header="Choose" Width="250" >
<tabData:ChooseFromDbTabItem DataContext="{Binding}" />
</TabItem>
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>