ItemsControl using WrapPanel, not displaying anything - c#

Goal: I am trying to create a wrap panel that has children that are bound to an observable collection.
Current Expected Behavior: I expect to see 3 nested wrap panels that have an ellipse, a text block, a label and a checkbox.
Problem: My wrap panel and contents are not displayed at runtime. (Note: "Test" and "Test 2" Labels outside of the itemscontrol do display as expected.)
I have read this and it doesn't seem to solve my problem.
Code Behind
using MVVM_SandBox.Models;
using MVVM_SandBox.ViewModels;
namespace MVVM_SandBox
{
public partial class MainWindow
{
public MainViewModel VMMain = new MainViewModel();
public MainWindow()
{
VMMain.SomeItemModelBlahs = new System.Collections.ObjectModel.ObservableCollection<ItemModelBlah>() { new ItemModelBlah() { Label = "blah0" }, new ItemModelBlah() { Label = "blah1", CoolStuff = true }, new ItemModelBlah() { Label = "blah2" } };
InitializeComponent();
}
}
}
XAML
<Window x:Name="winMain" x:Class="MVVM_SandBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodels="clr-namespace:MVVM_SandBox.ViewModels"
Title="MVVM SandBox" Height="600" Width="800" AllowDrop="True">
<Window.DataContext>
<viewmodels:MainViewModel></viewmodels:MainViewModel>
</Window.DataContext>
<StackPanel>
<Label Width="Auto" Height="Auto">Test</Label>
<ItemsControl ItemsSource="{Binding SomeItemModelBlahs}" Margin="10,10,10,10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="WrapPanelOfModelItems" Margin="10,10,10,10" Width="400" Height="200" IsItemsHost="True" MinWidth="200" MinHeight="200">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Width="100" Height="100" Margin="10">
<Ellipse Width="10" Height="10" Fill="Aqua"></Ellipse>
<TextBlock Margin="10" Text="{Binding Label}"></TextBlock>
<Label>kjasdkjalsdjklsad</Label>
<CheckBox IsChecked="{Binding CoolStuff}">
<Label>Hello</Label>
</CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Label Height="Auto" Width="Auto">Test 2</Label>
</StackPanel>
</Window>
View Model
using MVVM_SandBox.Models;
using System.Collections.ObjectModel;
namespace MVVM_SandBox.ViewModels
{
//[ImplementPropertyChanged]
public class MainViewModel
{
public ObservableCollection<ItemModelBlah> SomeItemModelBlahs { get; set; }
public MainViewModel()
{
}
}
}
Model
namespace MVVM_SandBox.Models
{
public class ItemModelBlah
{
public string Label { get; set; }
public bool CoolStuff { get; set; }
}
}

The code is creating two instances of MainViewModel: once in the code behind, and again in the XAML. The code behind instance has a non-null ObservableCollection, while the instance in the XAML is set as the DataContext.
I would suggest removing the viewmodels:MainViewModel from the XAML, and creating it solely in code:
public partial class MainWindow
{
public MainViewModel VMMain = new MainViewModel();
public MainWindow()
{
DataContext = VWMain;
InitializeComponent();
}
}
Also, it's a better design to set up the ObservableCollection from within the view model itself:
class MainViewModel
{
public ObservableCollection<ItemModelBlah> SomeItemModelBlahs { get; private set; }
public MainViewModel()
{
SomeItemModelBlahs = new ObservableCollection<ItemModelBlah>()
{
new ItemModelBlah() { Label = "blah0" },
new ItemModelBlah() { Label = "blah1", CoolStuff = true },
new ItemModelBlah() { Label = "blah2" }
};
}
}

Related

MVVM WPF TreeView in the MainWindow bound to the ObservableCollection doesn't update when the ObservableCollection was modified from another window

The problem might look similar to this
But I don't have an issue with updating the TreeView if the ObservableCollection is modified in the same window (as in the case of the linked post).
I have a TreeView located in my MainWindow. I use a HierarchicalDataTemplate for the TreeView, basically the TreeView is structured into three levels of hierarchy: Group, Report, Node
For each level of the TreeView, I have created simple Model classes, each class will have a Name property and an ObservableCollection of its children class as second property (e.g., Group class will have a Name and a ReportList property, whilst Report class will have a Name and NodeList property)
In the ViewModel of my MainWindow, I have an ObservableCollection of the Group objects (I named it Groups) that is bound to the TreeView. I also have a RelayCommand method that will add a new Group object to the Groups collection (Let's call it the ImportReport Method). If I bind the RelayCommand to the button inside the MainWindow, everything worked fine, the TreeView is updated immediately with new Group Object each time I press the button.
However, this becomes an issue when I have another window, let's call it ImportWindow. In ImpportWindow, I have a button. Probably this is the cause of the issue, but I use the same ViewModel for both the ImportWindow and the MainWindow so that they can share the Groups property that is bound to the TreeView in the MainWindow. My goal is to perform addition to the TreeView from the second window by pressing a button from there. Now, If I move the binding of the ImportReport command to the button in ImportWindow instead, then try to run the code, pressing the button on ImportWindowdoesn't update the TreeView at all. I have debugged the code and the Groups ObservableCollection in the ViewModel indeed recorded a new addition but the TreeView doesn't display the new Group object.
Any explanation to this? Thanks!
MainWindow
<Window x:Class="StressReportWPF.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:StressReportWPF"
xmlns:ribbon="clr-namespace:System.Windows.Controls.Ribbon;assembly=System.Windows.Controls.Ribbon"
xmlns:viewModel="clr-namespace:StressReportWPF.ViewModel"
xmlns:data="clr-namespace:StressReportWPF.DataElements"
mc:Ignorable="d"
Title="StressReportApp"
Height="750"
Width="1000">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type data:DisplayGroup}"
ItemsSource="{Binding ReportList}">
<TextBlock Text="{Binding DisplayGroupName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type data:DisplayReport}"
ItemsSource="{Binding NodeList}">
<TextBlock Text="{Binding DisplayReportName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type data:DisplayNode}">
<TextBlock Text="{Binding DisplayNodeName}"/>
</DataTemplate>
</Window.Resources>
<Border Background="#e7f4fe">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
Margin="0,20,20,20"
Background="#dde6ed"
BorderBrush="Black"
BorderThickness="1">
<ScrollViewer>
<TreeView Background="Transparent"
ItemsSource="{Binding Groups, UpdateSourceTrigger=PropertyChanged}">
</TreeView>
</ScrollViewer>
</Border>
<Border Grid.Column="1"
Margin="20,20,0,20"
Background="White"
BorderBrush="Black"
BorderThickness="1">
<ContentControl Margin="0"
Content="{Binding CurrentView}">
</ContentControl>
</Border>
</Grid>
</Grid>
</Border>
ImportWindow
<Window x:Class="StressReportWPF.ImportWindow"
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:StressReportWPF"
xmlns:viewModel="clr-namespace:StressReportWPF.ViewModel"
xmlns:data="clr-namespace:StressReportWPF.DataElements"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="ImportWindow" Height="450" Width="800"
Background="#dde6ed">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
<DataTemplate x:Key="GroupComboBoxTemplate">
<TextBlock Text="{Binding DisplayGroupName}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
...
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="150"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<Button Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="40"
Width="100"
Content="Import"
Style="{StaticResource StandardButtonStyle}"
Command="{Binding ImportCommand}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction MethodName="Close"
TargetObject="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType=Window}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</Grid>
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StressReportWPF.Core;
using StressReportWPF.DataElements;
using System.Collections.ObjectModel;
using System.Windows.Input;
using StressReportWPF.Views;
namespace StressReportWPF.ViewModel
{
public class MainViewModel: ObservableObject
{
#region ViewModel-related Properties
public RelayCommand HomePageViewCommand { get; set; }
public RelayCommand LoadCaseAssignmentViewCommand { get; set; }
public RelayCommand JointNumberAssignmentViewCommand { get; set; }
public HomePageViewModel HomePageVM { get; set; }
public LoadCaseAssignmentViewModel LoadCaseAssignmentVM { get; set; }
public JointNumberAssignmentViewModel JointNumberAssignmentVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
#endregion
private ICommand _importCommand;
public ICommand ImportCommand
{
get
{
if (_importCommand == null)
{
_importCommand = new RelayCommand(
param => ImportReport()
);
}
return _importCommand;
}
}
private ICommand _importWindowCommand;
public ICommand ImportWindowCommand
{
get
{
if (_importWindowCommand == null)
{
_importWindowCommand = new RelayCommand(
param => ShowImportWindow()
);
}
return _importWindowCommand;
}
}
#region Data Properties
private readonly ObservableCollection<DisplayGroup> _groups = new ObservableCollection<DisplayGroup>();
public ObservableCollection<DisplayGroup> Groups
{
get { return _groups; }
}
public bool IsNewGroupCreated { get; set; }
public object SelectedGroup { get; set; }
private DisplayGroup _newGroup = new DisplayGroup();
public DisplayGroup NewGroup
{
get { return _newGroup; }
set
{
_newGroup = value;
OnPropertyChanged();
}
}
#endregion
public MainViewModel()
{
HomePageVM = new HomePageViewModel();
LoadCaseAssignmentVM = new LoadCaseAssignmentViewModel();
JointNumberAssignmentVM = new JointNumberAssignmentViewModel();
CurrentView = HomePageVM;
HomePageViewCommand = new RelayCommand(o =>
{
CurrentView = HomePageVM;
});
LoadCaseAssignmentViewCommand = new RelayCommand(o =>
{
CurrentView = LoadCaseAssignmentVM;
});
JointNumberAssignmentViewCommand = new RelayCommand(o =>
{
CurrentView = JointNumberAssignmentVM;
});
// Set a temporary Group list
List<DisplayGroup> testGroupList = new List<DisplayGroup>();
List<DisplayNode> testNodeList = new List<DisplayNode>();
List<DisplayReport> testReportList = new List<DisplayReport>();
DisplayNode nodeA = new DisplayNode();
nodeA.DisplayNodeName = "Node A";
DisplayNode nodeB = new DisplayNode();
nodeB.DisplayNodeName = "Node B";
DisplayNode nodeC = new DisplayNode();
nodeC.DisplayNodeName = "Node C";
testNodeList.AddRange(new List<DisplayNode> { nodeA, nodeB, nodeC }) ;
ObservableCollection<DisplayNode> testNodeObsCol = new ObservableCollection<DisplayNode>(testNodeList);
DisplayReport reportA = new DisplayReport();
reportA.DisplayReportName = "Report A";
reportA.NodeList = testNodeObsCol;
DisplayReport reportB = new DisplayReport();
reportB.DisplayReportName = "Report B";
reportB.NodeList = testNodeObsCol;
DisplayReport reportC = new DisplayReport();
reportC.DisplayReportName = "Report C";
reportC.NodeList = testNodeObsCol;
testReportList.AddRange(new List<DisplayReport> { reportA, reportB, reportC });
ObservableCollection<DisplayReport> testReportObsCol = new ObservableCollection<DisplayReport>(testReportList);
DisplayGroup groupA = new DisplayGroup();
groupA.DisplayGroupName = "Group A";
groupA.ReportList = testReportObsCol;
DisplayGroup groupB = new DisplayGroup();
groupB.DisplayGroupName = "Group B";
groupB.ReportList = testReportObsCol;
DisplayGroup groupC = new DisplayGroup();
groupC.DisplayGroupName = "Group C";
testGroupList.AddRange(new List<DisplayGroup> { groupA, groupB, groupC });
foreach (var item in testGroupList)
{
Groups.Add(item);
}
}
public void ShowImportWindow()
{
//ImportViewModel importVM = new ImportViewModel();
//importVM.Groups = this.Groups;
ImportWindow importWindow = new ImportWindow();
//importWindow.DataContext = importVM;
importWindow.Show();
}
public void ImportReport()
{
if (IsNewGroupCreated)
{
DisplayGroup newGroup = new DisplayGroup();
newGroup.DisplayGroupName = "Group D";
Groups.Add(newGroup);
}
else
{
}
}
}
}

How to bind object property in ListView from another ListView

i am writing xamarin forecast weather app with MVVM.
I fill ObservableCollection<DailyWeather> and ObservableCollection<HourlyWeather>.
In ViewModel
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
OnPropertyChange();
}
}
Models
public class DailyWeather
{
public int DayOfYear { get; set; }
public ObservableCollection<HourlyWeather> HourlyWeather { get; set; }
}
public class HourlyWeather
{
public string Temperature { get; set; }
public string Time { get; set; }
}
Xaml code
<ListView ItemsSource="{Binding DailyWeather, Source={StaticResource vm}}"
RowHeight="200">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding DayOfYear}"/>
<ListView ItemsSource="{Binding HourlyWeather}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Temperature}"/>
<Label Text="{Binding Time}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Parent ListView outputs collection and "DayOfYear".
Child ListView Outputs Collection and sees object properties "Temperature" and "Time" but not output them, why?
All collections are filled.
Why if i delete ViewCell from ListViews then app thow "specified cast is not valid" exception?
You need to remove this Source={StaticResource vm} from your binding.
Add this to your XAML:
<ContentPage.BindingContext>
<vm:VIEWMODEL></vm:VIEWMODEL>
</ContentPage.BindingContext>
And change parent listview to this:
<ListView ItemsSource="{Binding DailyWeather}" RowHeight="200">
Remove the Source={StaticResource vm} in ItemSource of your ListView. And set the code below in the code behind of your page.
this.BindingContext = this;
The whole code:
public partial class Page1 : ContentPage
{
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
}
}
public Page1()
{
InitializeComponent();
DailyWeather = new ObservableCollection<DailyWeather>()
{
new DailyWeather()
{
DayOfYear=2011,
HourlyWeather=new ObservableCollection<HourlyWeather>()
{
new HourlyWeather()
{
Temperature="1",
Time="2011-01-02"
},
new HourlyWeather()
{
Temperature="2",
Time="2011-01-03"
},
new HourlyWeather()
{
Temperature="3",
Time="2011-01-04"
},
new HourlyWeather()
{
Temperature="4",
Time="2011-01-05"
}
}
}
};
this.BindingContext = this;
}
}
public class DailyWeather
{
public int DayOfYear { get; set; }
public ObservableCollection<HourlyWeather> HourlyWeather { get; set; }
}
public class HourlyWeather
{
public string Temperature { get; set; }
public string Time { get; set; }
}
Screenshot:
If you using mvvm , you should use Locator. Here is working example. Changed some tags because dont have WPF now at pc.
YourVM.cs
class YourVM : INotifyPropertyChanged
{
private ObservableCollection<DailyWeather> dailyWeather;
public ObservableCollection<DailyWeather> DailyWeather
{
get => dailyWeather;
set
{
dailyWeather = value;
OnPropertyChange(nameof(DailyWeather));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public YourVM()
{
DailyWeather = new ObservableCollection<DailyWeather>()
{
new DailyWeather()
{
DayOfYear=2011,
HourlyWeather=new ObservableCollection<HourlyWeather>()
{
new HourlyWeather()
{
Temperature="1",
Time="2011-01-02"
},
new HourlyWeather()
{
Temperature="2",
Time="2011-01-03"
},
new HourlyWeather()
{
Temperature="3",
Time="2011-01-04"
},
new HourlyWeather()
{
Temperature="4",
Time="2011-01-05"
}
}
}
};
}
}
MVVMLocator.cs
class MVVMLocator
{
public MVVMLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<YourVM>();
}
public static YourVM YourVM => SimpleIoc.Default.GetInstance<YourVM>();
}
App.xaml
<Application
x:Class="App2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App2">
<Application.Resources>
<ResourceDictionary>
<local:MVVMLocator x:Key="Locator"/>
</ResourceDictionary>
</Application.Resources>
</Application>
MainPage.xaml
<Page
x:Class="App2.MainPage"
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"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView ItemsSource="{Binding YourVM.DailyWeather , Source={StaticResource Locator}}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Height="200">
<TextBlock Text="{Binding DayOfYear}"/>
<ListView ItemsSource="{Binding HourlyWeather}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Temperature}"/>
<TextBlock Text="{Binding Time}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Nuget: CommonServiceLocator , MvvmLightLibs

Proper databinding in WPF using TabControl and MVVM

Just when I thought I was getting better at this, TabControl is now giving me problems. I have read relevant posts here on StackOverflow, but have been unable to get my simple demo application to work the way I want it to.
To keep things focused, I'll start with a single question about something I don't understand.
I have a TabControl whose TabItems each host the same UserControl. When I set the TabControl.ContentTemplate's DataTemplate to my UserControl, a rendering of that control appears, but it looks like it's the same control for each tab. Or perhaps it's not tied to any of the tabs at all.
MainWindow.xaml
<Window x:Class="TabControlMvvm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:localviews="clr-namespace:TabControlMvvm.Views"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind just sets the ViewModel as its DataContext:
namespace TabControlMvvm {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow()
{
InitializeComponent();
DataContext = new TabControlMvvm.ViewModels.MainViewModel();
}
}
}
The TabItem's Content should be another UserControl, PersonMainPanel.xaml:
<UserControl x:Class="TabControlMvvm.Views.PersonMainPanel"
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:localviews="clr-namespace:TabControlMvvm.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border BorderBrush="Red" BorderThickness="2">
<TabControl TabStripPlacement="Bottom">
<TabItem Header="Tab 1">
<localviews:MyTabItem />
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="This was left blank intentionally" />
</TabItem>
<TabItem Header="Tab 3">
<TextBlock Text="This was also left blank intentionally" />
</TabItem>
</TabControl>
</Border>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for PersonMainPanel.xaml
/// </summary>
public partial class PersonMainPanel : UserControl {
public PersonMainPanel()
{
InitializeComponent();
}
}
}
And the MainViewModel:
namespace TabControlMvvm.ViewModels {
public class MainViewModel : ViewModelBase {
public ICollectionView Tabs { get; set; }
public int Selected { get; set; }
public class Person
{
public string Name { get; set; }
}
public class DummyController {
public List<Person> Persons { get; private set; }
public DummyController()
{
Persons = new List<Person> {
new Person { Name = "Larry" },
new Person { Name = "Darryl" },
new Person { Name = "Other brother Darryl" }
};
}
}
public DummyController Controller { get; private set; }
public RelayCommand HelloCommand { get; set; }
public MainViewModel()
{
Controller = new DummyController();
/*
IEnumerable<TabItem> tabs = Enumerable.Range( 1, _controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x),
Content = new PersonMainPanel() });
*/
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format( "Person {0}", x)});
Tabs = CollectionViewSource.GetDefaultView( tabs.ToList());
Tabs.MoveCurrentToFirst();
InitializeCommands();
}
private void InitializeCommands()
{
HelloCommand = new RelayCommand( () => { MessageBox.Show( String.Format( "Hello, Person {0} named {1}!",
Selected, Controller.Persons[Selected].Name)); });
}
}
}
PersonMainPanel hosts another TabControl, where Tab 1's Content is MyTabItem.xaml:
<UserControl x:Class="TabControlMvvm.Views.MyTabItem"
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">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" Width="100" />
</StackPanel>
<Button Command="{Binding HelloCommand}" Content="Say Hello" />
</StackPanel>
</UserControl>
Code-behind:
namespace TabControlMvvm.Views {
/// <summary>
/// Interaction logic for MyTabItem.xaml
/// </summary>
public partial class MyTabItem : UserControl {
public MyTabItem()
{
InitializeComponent();
}
}
}
Which looks like this at runtime:
Issues I have so far:
When I enter Person 1's Name and then click the Person 2 tab, Person 1's Name is still visible, hence my assumption that the controls are not databound properly. I understand that ItemsControls do not pass their DataContext down to their children, but I am not sure how to fix this without associating the View in code-behind.
I would have expected to get databinding errors in the Output window because of the missing DataContext, but I don't get any errors. I assume the DataContext is null, but wouldn't this still result in a binding error?
How can I use Snoop effectively to debug problems like this?
Here's the sample solution: http://www.filedropper.com/tabcontrolmvvm
Here is solution:
In MainWindow modify your TabControl template, to bind Header from your Model:
<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="{Binding Selected}">
<TabControl.ContentTemplate>
<DataTemplate>
<localviews:PersonMainPanel />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
In MyTabItem.xaml, set UpdateTrigger, because default one 'OnLostFocus' can sometimes not save your data:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="100" />
In MainViewModel modify creating of your tabs, so it will have Name property too:
IEnumerable<TabItem> tabs = Enumerable.Range( 1, Controller.Persons.Count())
.Select( x => new TabItem { Header = String.Format("Person {0}", x), Name = Controller.Persons[x-1].Name });
Also, the most important, create own TabItem class to contain some bounded data:
public class TabItem
{
public string Name { set; get; }
public string Header { set; get; }
}

Why are my User Settings saved only the first time this method is called?

I have a WPF MVVM app, which gets its data from a user setting which is an ObservableCollection of type Copyable (a custom class) called Copyables. Within the main view model (ClipboardAssistantViewModel), I set the source of a CollectionViewSource to Copyables. This is then bound to an ItemsControl in the main view (MainWindow). The DataTemplate for this ItemsControl is a user control, 'CopyableControl', which is essentially a button, but with attached properties that allow me to bind data and commands to it.
When a user clicks on a CopyableControl, a view model (DefineCopyableViewModel) is added to an ObservableCollection of those in ClipboardAssistantViewModel, and that collection is bound to an ItemsControl in MainWindow. The DataTemplate of this is a UserControl called DefineCopyableControl, which is set up in such a way that the current values associated with the clicked Copyable are bound to textboxes in the DefineCopyableControl for editing.
My problem: There is a method in DefineCopyableViewModel, EditCopyable(), which only works on the first run (its job is to save the user settings once any edits have taken place and the user clicks "OK"). If I click the CopyableControl and make an edit, then click "OK", then click it again, make another edit, then click "OK", then close the application and open it again, only the first edit has been saved (even though the UI was updated with the edited value both times). It seems to have something to do with the data-binding need to be "refreshed"; please see the comments in this method in the code for my findings around this.
My code is as follows:
Model:
namespace ClipboardAssistant.Models
{
public class Copyable : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private string textToCopy;
public string TextToCopy
{
get { return textToCopy; }
set
{
if (value != textToCopy)
{
textToCopy = value;
NotifyPropertyChanged("TextToCopy");
}
}
}
public Copyable() { }
public Copyable(string Name, string TextToCopy)
{
this.Name = Name;
this.TextToCopy = TextToCopy;
}
}
}
ViewModels:
namespace ClipboardAssistant.ViewModels
{
public class ClipboardAssistantViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public CollectionViewSource CopyablesView { get; set; }
public ObservableCollection<DefineCopyableViewModel> Definers { get; set; }
public CopyableClickCommand CopyableClickCommand { get; set; }
public ClipboardAssistantViewModel()
{
Definers = new ObservableCollection<DefineCopyableViewModel>();
CopyablesView = new CollectionViewSource();
CopyablesView.Source = Properties.Settings.Default.Copyables;
CopyableClickCommand = new CopyableClickCommand(this);
EditModeClickCommand = new EditModeClickCommand(this);
}
public void RefreshCopyables()
{
// Both these methods of refreshing appear to have the same effect.
Properties.Settings.Default.Copyables = (ObservableCollection<Copyable>)CopyablesView.Source;
CopyablesView.Source = Properties.Settings.Default.Copyables;
}
public void EditCopyable(Copyable Copyable)
{
Definers.Add(new DefineCopyableViewModel(Copyable, this));
}
}
}
namespace ClipboardAssistant.ViewModels
{
public class DefineCopyableViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public ClipboardAssistantViewModel MyParent { get; set; }
public Copyable Copyable { get; set; }
public DefinerOKClickCommand DefinerOKClickCommand { get; set; }
public DefineCopyableViewModel(Copyable Copyable, ClipboardAssistantViewModel MyParent)
{
this.Copyable = Copyable;
this.MyParent = MyParent;
DefinerOKClickCommand = new DefinerOKClickCommand(this);
}
public void EditCopyable()
{
// Refresh, save, no refresh, save -> doesn't save second edit.
// Save, refresh, save, no refresh -> does save second edit.
MessageBoxResult r = MessageBox.Show("Refresh?", "Refresh", MessageBoxButton.YesNo);
if (r == MessageBoxResult.Yes)
{
MyParent.RefreshCopyables();
}
// These two MessageBox methods (save and refresh) can be swapped around (see above comments).
MessageBoxResult s = MessageBox.Show("Save?", "Save", MessageBoxButton.YesNo);
if (s == MessageBoxResult.Yes)
{
Properties.Settings.Default.Save();
}
MyParent.Definers.Remove(this);
}
}
}
MainWindow:
<Window x:Class="ClipboardAssistant.Views.MainWindow" x:Name="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:vm="clr-namespace:ClipboardAssistant.ViewModels"
xmlns:ctrls="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d"
Title="Clipboard Assistant" Height="400" Width="700">
<Window.DataContext>
<vm:ClipboardAssistantViewModel />
</Window.DataContext>
<Grid>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding CopyablesView.View}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:CopyableControl Copyable="{Binding}"
ClickCopyable="{Binding DataContext.CopyableClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DockPanel Grid.Row="2">
<Button x:Name="btnEditCopyableMode" HorizontalAlignment="Left" DockPanel.Dock="Left"
Content="Edit" Margin="0,0,10,0" Command="{Binding EditModeClickCommand}" />
</DockPanel>
</Grid>
<ItemsControl ItemsSource="{Binding Definers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:DefineCopyableControl Copyable="{Binding DataContext.Copyable}"
ClickCancel="{Binding DataContext.DefinerCancelClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
CopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.CopyableControl" x:Name="copyableControl"
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:ClipboardAssistant.Controls"
mc:Ignorable="d" d:DesignHeight="75" d:DesignWidth="200">
<Grid Width="200" Height="75">
<Button Command="{Binding ClickCopyable, ElementName=copyableControl}"
CommandParameter="{Binding Copyable, ElementName=copyableControl}"
Content="{Binding Copyable.Name, ElementName=copyableControl}"
Style="{StaticResource CopyableMainButtonStyle}" />
</Grid>
</UserControl>
namespace ClipboardAssistant.Controls
{
public partial class CopyableControl : UserControl
{
public static readonly DependencyProperty ClickCopyableProperty =
DependencyProperty.Register("ClickCopyable", typeof(ICommand), typeof(CopyableControl));
public ICommand ClickCopyable
{
get { return (ICommand)GetValue(ClickCopyableProperty); }
set { SetValue(ClickCopyableProperty, value); }
}
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(CopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public CopyableControl()
{
InitializeComponent();
}
}
}
DefineCopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.DefineCopyableControl" x:Name="defineCopyableControl"
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="500">
<Grid x:Name="MainGrid" Background="Blue">
<Grid Width="200" Height="180">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="10" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Name" Foreground="White" />
<TextBox Grid.Row="1" Text="{Binding Copyable.Name}" x:Name="tbN" />
<Label Grid.Row="3" Content="Copyable Text" Foreground="White" />
<TextBox Grid.Row="4" Text="{Binding Copyable.TextToCopy}" x:Name="tbTTC" />
<DockPanel Grid.Row="6">
<Button Width="70" Content="OK" DockPanel.Dock="Right" HorizontalAlignment="Right"
Command="{Binding DefinerOKClickCommand}"
CommandParameter="{Binding ElementName=defineCopyableControl}" />
</DockPanel>
</Grid>
</Grid>
</UserControl>
public partial class DefineCopyableControl : UserControl
{
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(DefineCopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public DefineCopyableControl()
{
InitializeComponent();
}
}
Commands:
namespace ClipboardAssistant.ViewModels.Commands
{
public class CopyableClickCommand : ICommand
{
public ClipboardAssistantViewModel ViewModel { get; set; }
public CopyableClickCommand(ClipboardAssistantViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Copyable cp = (Copyable)parameter;
ViewModel.EditCopyable(cp);
}
}
}
namespace ClipboardAssistant.ViewModels.Commands
{
public class DefinerOKClickCommand : ICommand
{
public DefineCopyableViewModel ViewModel { get; set; }
public DefinerOKClickCommand(DefineCopyableViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.EditCopyable();
}
}
}
Settings:
namespace ClipboardAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable> Copyables {
get {
return ((global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable>)(this["Copyables"]));
}
set {
this["Copyables"] = value;
}
}
}
}
I'm assuming you are using Visual Studio. In that case, in the My Project do you have the settings listed in the settings tab?
I ran into the same issue a while back where I tried to programatically create/save/update settings and was unsucessful until I created the setting in the Settings tab. Once that was complete I was able to make my saves as necessary.
The you just use
MySettings.Default.SettingName = value
MySettings.Default.Save()
Hope this helps!

How to bind events and properties of controls in UniformGrid dynamically?

I am a newbie in templating Wpf controls. I use VS2013, WPF 4.5 and Caliburn Micro 2.0.2. In part of tasks I have I need to populate a grid with toggle buttons contained different images and its subtitle. I have solved it using UniformGrid. See my code below. They work but still don't have event and property binding since I don't know how I can bind the events and properties of toggle buttons to view model, since they are generated automatically and dynamically and the number of toggle buttons is uncertain (depends on the number of images in the image folder).
For example:
manually I could bind the Click event, IsChecked property and some other properties of toggle button 1 like following:
<ToggleButton x:Name="ToggleVehicle01" IsChecked={Binding SelectedVehicle01} Background="{Binding BackColorSelectedVehicle01}" ToolTip="{Binding VehicleName01}">
But now I can't do that anymore since the toggle buttons are generated automatically and their number is uncertain. Please help. Feel free to change my code below or give me examples code that works. Thank you in advance.
The View (MainView.xaml):
<UserControl x:Class="CMWpf02.Views.MainView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="1024"
Height="768"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ShowGridLines="True">
<ItemsControl Name="ImageList"
Background="#FFFFFFFF"
BorderBrush="#FFA90606"
ItemsSource="{Binding Path=VehicleImages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Margin="0,0,0,0" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Width="180"
Margin="10,10,10,10"
FontSize="10"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}">
<!-- x:Name="ToggleVehicle01" -->
<!-- Background="{Binding BackColorSelectedVehicle01}" -->
<!-- IsChecked="{Binding SelectedVehicle01}" -->
<!-- ToolTip="{Binding Vehicle01Name}"> -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel (MainViewModel.cs):
using Caliburn.Micro;
using System;
using System.Collections.ObjectModel;
using System.IO;
namespace CMWpf02.ViewModels
{
public class MainViewModel : Screen, IHaveDisplayName
{
private String _path2Images = #"D:\tmp\Images";
public string DisplayName { get; set; }
public ObservableCollection<VehicleImage> VehicleImages { get; set; }
public MainViewModel()
{
DisplayName = "Main Window";
var vehicles = new ObservableCollection<String>();
vehicles = GetAllFilesFromFolder(_path2Images);
VehicleImages = new ObservableCollection<VehicleImage>();
foreach (var i in vehicles)
VehicleImages.Add(new VehicleImage(i));
}
public ObservableCollection<String> GetAllFilesFromFolder(String fullPathFolder)
{
string[] fileArray = Directory.GetFiles(fullPathFolder);
return new ObservableCollection<String>(fileArray);
}
}
public class VehicleImage
{
public String Image { get; private set; }
public String Name { get; private set; }
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
//public void ToggleVehicle01()
//{
// var selectText = (SelectedVehicle01) ? " selected" : " unselected";
// MessageBox.Show(Vehicle01Name + selectText);
// BackColorSelectedVehicle01 = (SelectedVehicle01) ? _backColorSelectedVehicle : _defaultBackColorVehicle;
//}
//public Boolean SelectedVehicle02
//{
// get { return _selectedVehicle02; }
// set
// {
// _selectedVehicle02 = value;
// NotifyOfPropertyChange(() => SelectedVehicle02);
// }
//}
//public Brush BackColorSelectedVehicle02
//{
// get { return _backColorSelectedVehicle02; }
// set
// {
// _backColorSelectedVehicle02 = value;
// NotifyOfPropertyChange(() => BackColorSelectedVehicle02);
// }
//public String Vehicle01Name { get; private set; }
}
EDIT: Now I can bind the properties of generated ToggleButton with view model. I make the VehicleImage class to a view model (see modified code below). But I still have problem to bind Click-event of generated ToggleButton to view model.
The modified class to view model
public class VehicleImage : PropertyChangedBase
{
public String Image { get; private set; }
public String Name { get; private set; }
private Boolean _selectedVehicle;
public Boolean SelectedVehicle
{
get { return _selectedVehicle; }
set
{
_selectedVehicle = value;
BackColorSelectedVehicle = _selectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
}
private Brush _backColorSelectedVehicle;
public Brush BackColorSelectedVehicle
{
get { return _backColorSelectedVehicle; }
set
{
_backColorSelectedVehicle = value;
NotifyOfPropertyChange(() => BackColorSelectedVehicle);
}
}
// ToggleButton's Click-Event Handler, but it doesn't get event trigger from View.
// Therefore I set the BackColorSelectedVehicle fin setter of SelectedVehicle property.
public void ToggleSelection()
{
//BackColorSelectedVehicle = SelectedVehicle ? new SolidColorBrush(Color.FromArgb(255, 242, 103, 33)) : new SolidColorBrush(Colors.White);
}
public VehicleImage(String image)
{
Image = image;
Name = Path.GetFileName(image);
}
}
The modified view
<ToggleButton Width="180"
Margin="10,10,10,10"
Background="{Binding Path=BackColorSelectedVehicle}"
FontSize="10"
IsChecked="{Binding Path=SelectedVehicle}"
Style="{StaticResource {x:Static ToolBar.ToggleButtonStyleKey}}"
ToolTip="{Binding Path=Name}">
<!-- x:Name="ToggleSelection" -->
<StackPanel Margin="0,5,0,5"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image Width="140"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Path=Image}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=Name}" />
</StackPanel>
</ToggleButton>

Categories