Extending A ListBox with an ItemTemplate - c#

I need to extend a ListBox with a custom ItemTemplate but when I run my code the ItemTemplate does not get applied?
<ListBox x:Class="ExtendedCheckedListbox"
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:ExtListBoxPOC"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Description}" VerticalAlignment="Stretch" VerticalContentAlignment="Center" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
private YesNoModel YesNo = new YesNoModel();
{
DataContext = YesNo;
cbl.ItemsSource = YesNo;
}
My main Window XAML which uses the control called cbl which has the ItemsSource set in code behind:
<Window x:Class="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:ExtListBoxPOC"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:ExtendedCheckedListbox x:Name="cbl" HorizontalAlignment="Left" Height="300" Margin="10" VerticalAlignment="Top" Width="300"/>
</Grid>
</Window>
The Model class is this:
public class YesNoModel
{
public string Description { get; set; }
public int Value { get; set; }
}
And I am adding items here:
{
YesNo.Add(new YesNoModel() { Description = "Yes", Value = 1 });
YesNo.Add(new YesNoModel() { Description = "No", Value = 2 });
YesNo.Add(new YesNoModel() { Description = "N/A", Value = 3 });
}
Code behind the ExtendedCheckedListbox View:
public class ExtendedCheckedListbox : ListBox
{
}

Your derived ListBox simply ignores the XAML, because you did apparently not call InitializeComponent() anywhere.
However, the usual way to derive from a control is to create a default Style in Themes\Generic.xaml. Add a "custom control" to your Visual Studio project and modify it like this:
public class ExtendedCheckedListBox : ListBox
{
static ExtendedCheckedListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(ExtendedCheckedListBox),
new FrameworkPropertyMetadata(typeof(ExtendedCheckedListBox)));
}
}
Then change the content of the generated Themes\Generic.xaml file to this:
<Style TargetType="local:ExtendedCheckedListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox Content="{Binding Description}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
See Control Authoring Overview for details.

Related

How to set up a dynamically-populated TabControl with ReactiveUI?

I'm trying to wrap my head around ReactiveUI. Most of it makes some degree of sense if you don't look at it too closely, but when I try to set up a TabControl everything explodes in my face.
I have a TabControl on my Window. I want to be able to add multiple different types of tabs to it dynamically at runtime based on different user actions. This answer explains a standard WPF way to do that, and it almost works, but since it's not ReactiveUI, anytime I try to open a tab with a Reactive view on it, everything blows up because the ViewModel dependency property hasn't been bound.
XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MyTabViewModel}">
<views:MyTabEditor/>
</DataTemplate>
</Window.Resources>
...
<TabControl Name="Multitab" Grid.Column="2" ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ViewModel:
public ObservableCollection<ITabPage> Tabs { get; } = new();
public void AddNewTab()
{
var vm = new MyTabEditorViewModel();
Tabs.Add(vm);
}
XAML.cs
private void NewTab_Executed(object sender, ExecutedRoutedEventArgs e)
{
ViewModel.AddNewJob();
Multitab.SelectedIndex = Multitab.Items.Count - 1;
var last = Multitab.SelectedIndex;
this.OneWayBind(ViewModel, vm => vm.Tabs[last], v => GAH WHAT GOES HERE?!?);
}
And this is the part where I get lost. How do I set up the bindings to bind the new VM to the view that gets created for it? Multitab.SelectedItem returns the VM, not the View, and I can't seem to find any way to obtain the newly-created View object in order to bind it.
Does anyone know how to set this up properly?
Please refer to the below sample code.
Window1.xaml:
<reactiveui:ReactiveWindow x:Class="WpfApp1.Window1"
x:TypeArguments="local:ViewModel"
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:WpfApp1"
xmlns:reactiveui="http://reactiveui.net"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<Grid>
<TabControl Name="Multitab">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<reactiveui:ViewModelViewHost ViewModel="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</reactiveui:ReactiveWindow>
Window.xaml.cs:
public partial class Window1 : ReactiveWindow<ViewModel>
{
public Window1()
{
InitializeComponent();
ViewModel = new ViewModel();
this.WhenActivated(disposableRegistration =>
{
this.OneWayBind(ViewModel,
viewModel => viewModel.Tabs,
view => view.Multitab.ItemsSource)
.DisposeWith(disposableRegistration);
});
}
}
View Model:
public class ViewModel
{
public ObservableCollection<ITabPage> Tabs { get; } =
new ObservableCollection<ITabPage>() { new MyTabEditorViewModel() };
}
Tab View Model:
public interface ITabPage { }
public class MyTabEditorViewModel : ITabPage
{
public string Name { get; } = "Name...";
}
TabView.xaml:
<reactiveui:ReactiveUserControl x:Class="WpfApp1.TabEditorView"
x:TypeArguments="local:MyTabEditorViewModel"
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:reactiveui="http://reactiveui.net"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock>Tab content...</TextBlock>
</Grid>
</reactiveui:ReactiveUserControl>
TabView.xaml.cs:
public partial class TabEditorView : ReactiveUserControl<MyTabEditorViewModel>
{
public TabEditorView()
{
InitializeComponent();
}
}

ContentControl not refreshing View when new ViewModel is created

I am trying to make an app using using MVVMLight that has a sidebar with buttons, each button shows a new usercontrol hosted in a ContentControl. When the user clicks the buttons in the sidebar (Show View1 / Show View2) it shows the views correctly.
My problems arise when the user has already shown a View (it could be View1 or View2 ), say View1 and click (Show View1) ** again ** View1 controls remain with the same values and I would like to show the same view with all controls as if it had restarted. (I know that I could reset all controls within the user control (View1) but my application requires having a new instance of the view every time the button is clicked).
Somehow, in the same scenario when the user is in View 1, switch to view 2 and return to view 1, a new instance is created and shown as I would like. I would like to get the same result without the need to switch to view 2 and return.
Here is a minimal reproducible example of the issue:
ViewModelLocator.cs
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
namespace SidebarApp.ViewModel
{
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register viewmodels
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<View1ViewModel>();
SimpleIoc.Default.Register<View2ViewModel>();
}
public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
public View1ViewModel View1Vm { get { return ServiceLocator.Current.GetInstance<View1ViewModel>(); } }
public View2ViewModel View2Vm { get { return ServiceLocator.Current.GetInstance<View2ViewModel>(); } }
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Input;
namespace SidebarApp.ViewModel
{
public class MainViewModel : ViewModelBase
{
// Commands
public ICommand ShowView1Command { get; private set; }
public ICommand ShowView2Command { get; private set; }
/// <summary>
/// Property that will cointain the current viewmodel to show
/// ViewModelBase inplements ObservableObject class which imlements INotifyPropertyChanged
/// </summary>
public ViewModelBase CurrentViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
this.ShowView1Command = new RelayCommand(ShowView1);
this.ShowView2Command = new RelayCommand(ShowView2);
}
private void ShowView1()
{
// I tried this but it doesn't work
//CurrentViewModel = null or View2ViewModel;
CurrentViewModel = new View1ViewModel();
}
private void ShowView2()
{
CurrentViewModel = new View2ViewModel();
}
}
}
MainWindow.xaml
<Window x:Class="SidebarApp.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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="MainWindow" Height="348.965" Width="560.683">
<Window.Resources>
<DataTemplate DataType="{x:Type local:View1ViewModel}">
<local:View1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:View2ViewModel}">
<local:View2View />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Command="{Binding ShowView1Command}">Show View1</Button>
<Button Command="{Binding ShowView2Command}">Show View2</Button>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding Path=CurrentViewModel}"></ContentControl>
</Grid>
</Window>
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View1Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Original text value"/>
<CheckBox Content="Some boolean value"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View1ViewModel : ViewModelBase
{
public View1ViewModel()
{
}
}
}
View2View.xaml
<UserControl x:Class="SidebarApp.View2View"
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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View2Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightCyan">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 2" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Some text in view2"/>
<Button Content="Button"/>
</StackPanel>
</Grid>
</UserControl>
View2ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View2ViewModel : ViewModelBase
{
public View2ViewModel()
{
}
}
}
App.xaml
<Application x:Class="SidebarApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SidebarApp" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:SidebarApp.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
For example I enter to the view1 and change the value of the textbox ("Original text value") to another value and I click again in Show View1 the text and everything in the usercontrol stays the same but when I switch to view2 and go back to view1 a new usercontrol is shown with his original values.
This might feel like something of a workaround, but it works. Hopefully it will prove useful. (Obviously make same changes to View2View view/view model).
ViewModelLocator.cs
public View1ViewModel View1Vm { get { return new View1ViewModel(); } }
MainViewViewModel.cs
private void ShowView1()
{
CurrentViewModel = new ViewModelLocator().View1Vm;
}
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
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:SidebarApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="{Binding Path=View1Text, Mode=TwoWay}"/>
<CheckBox Content="Some boolean value" IsChecked="{Binding Path=CheckBoxChecked, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
public class View1ViewModel : ViewModelBase
{
private string _view1Text;
private bool _checkBoxedChecked;
public string View1Text
{
get
{
return _view1Text;
}
set
{
_view1Text = value;
RaisePropertyChanged("View1Text");
}
}
public bool CheckBoxChecked
{
get
{
return _checkBoxedChecked;
}
set
{
_checkBoxedChecked = value;
RaisePropertyChanged("CheckBoxChecked");
}
}
public View1ViewModel()
{
View1Text = "Original text value";
}
}

Add Tabs and TabItem content from ViewModel

I have a ViewModel from which I am trying to populate a TabControl. What I want is to create tabs and all the controls in tab items from the view model.
Right now Tabs are created correctly, but the user control "CustomList" (added from Collection in the Tab class) is being added vertically in each tab and is stretched horizontally. What I want to do is to add CustomLists horizontally and it should stretch vertically.
My question is how can I add CustomList from the Collection in ViewModel in TabItem horizontally and stretched vertically?
Thanks in advance.
The code is:
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
xmlns:models="clr-namespace:WpfApplication1.Models"
xmlns:comps="clr-namespace:WpfApplication1.Components"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<models:ViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DockPanel>
<!-- this is where user controls are added vertically -->
<ItemsControl ItemsSource="{Binding CustomLists}" DockPanel.Dock="Left" />
</DockPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
CustomList.xaml
<UserControl x:Class="WpfApplication1.Components.CustomList"
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:WpfApplication1.Components"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel LastChildFill="True">
<Label DockPanel.Dock="Top" Content="Title" x:Name="title" />
<ListView x:Name="listView" />
</DockPanel>
</UserControl>
Tab.cs
namespace WpfApplication1.Models
{
public class Tab
{
public String Header { get; set; }
public ObservableCollection<CustomList> CustomLists { get; set; } = new ObservableCollection<CustomList>();
}
}
ViewModel.cs
namespace WpfApplication1.Models
{
public class ViewModel
{
public ObservableCollection<Tab> Tabs { get; set; } = new ObservableCollection<Tab>();
public ViewModel()
{
var tab1 = new Tab();
tab1.Header = "Tab 1";
tab1.CustomLists.Add(new Components.CustomList());
tab1.CustomLists.Add(new Components.CustomList());
tab1.CustomLists.Add(new Components.CustomList());
Tabs.Add(tab1);
var tab2 = new Tab();
tab2.Header = "Tab 2";
tab2.CustomLists.Add(new Components.CustomList());
Tabs.Add(tab2);
var tab3 = new Tab();
tab3.Header = "Tab 3";
tab3.CustomLists.Add(new Components.CustomList());
tab3.CustomLists.Add(new Components.CustomList());
Tabs.Add(tab3);
}
}
}
---EDIT--- Based on Rachel's comment
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
xmlns:models="clr-namespace:WpfApplication1.Models"
xmlns:comps="clr-namespace:WpfApplication1.Components"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<models:ViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding CustomLists}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
CustomListControl.xaml
**<UserControl x:Class="WpfApplication1.Components.CustomListControl"
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:WpfApplication1.Components"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel LastChildFill="True">
<Label DockPanel.Dock="Top" Content="Title" x:Name="title" />
<ListView x:Name="listView" Width="300" />
</DockPanel>
</UserControl>**
CustomList.cs
namespace WpfApplication1.Models
{
public class CustomList
{
public String Title { get; set; }
}
}
Tab.cs
namespace WpfApplication1.Models
{
public class Tab
{
public String Header { get; set; }
public ObservableCollection<CustomList> CustomLists { get; set; } = new ObservableCollection<CustomList>();
}
}
ViewModel.xaml
namespace WpfApplication1.Models
{
public class ViewModel
{
public ObservableCollection<Tab> Tabs { get; set; } = new ObservableCollection<Tab>();
public ViewModel()
{
var tab1 = new Tab();
tab1.Header = "Tab 1";
tab1.CustomLists.Add(new CustomList());
tab1.CustomLists.Add(new CustomList());
tab1.CustomLists.Add(new CustomList());
Tabs.Add(tab1);
var tab2 = new Tab();
tab2.Header = "Tab 2";
tab2.CustomLists.Add(new CustomList());
Tabs.Add(tab2);
var tab3 = new Tab();
tab3.Header = "Tab 3";
tab3.CustomLists.Add(new CustomList());
tab3.CustomLists.Add(new CustomList());
Tabs.Add(tab3);
}
}
}

Visibility of one user control to another user control

private void Button_Click(object sender, RoutedEventArgs e)
{
int selectedValue = (int)comboSelection.SelectedValue;
if (selectedValue == 8)
{
EightTiles et = new EightTiles();
this.Visibility = Visibility.Collapsed;
et.Visibility = Visibility.Visible;
}
}
My target is when the combo box selection is equal to 8 then click the button, current usercontrol get collapsed and next usercontrol(EightTiles) get visible.
But my problem is when i click the button it shows a blank page, next user control page doesn't showed, what is the problem and how i do to solve it..
Thanks
Plase both these controls as content of a main content control to xaml, and manage the second control visibility based on trigger of the first control. Here what I can suggest you:
1. XAML:
<Window x:Class="SoDataGridProjectsHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:soDataGridProjectsHelpAttempt="clr-namespace:SoDataGridProjectsHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ContentControl >
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid>
<soDataGridProjectsHelpAttempt:MainSubControl x:Name="MainSubControl" Visibility="Visible"/>
<soDataGridProjectsHelpAttempt:SubSubControl x:Name="SubSubControl" Visibility="Collapsed"/>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="Control.Visibility" Value="Collapsed" SourceName="MainSubControl">
<Setter TargetName="SubSubControl" Property="Visibility" Value="Visible"></Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
2. MainSubControl:
<UserControl x:Class="SoDataGridProjectsHelpAttempt.MainSubControl"
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:soDataGridProjectsHelpAttempt="clr-namespace:SoDataGridProjectsHelpAttempt"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsControlVisible, Converter={StaticResource Bol2VisibilityConverter}, UpdateSourceTrigger=PropertyChanged}">
<UserControl.Resources>
<x:Array Type="system:Int32" x:Key="DecimalsArray">
<system:Int32>7</system:Int32>
<system:Int32>5</system:Int32>
<system:Int32>3</system:Int32>
<system:Int32>8</system:Int32>
</x:Array>
</UserControl.Resources>
<UserControl.DataContext>
<soDataGridProjectsHelpAttempt:MainSubViewModel/>
</UserControl.DataContext>
<StackPanel>
<ComboBox ItemsSource="{StaticResource DecimalsArray}"
Width="Auto"
SelectedItem="{Binding SelectedComboItem}"/>
<Button Command="{Binding Command}">Press me!!!</Button>
</StackPanel>
3. MainSubControl ViewModel:
public class MainSubViewModel : BaseObservableObject
{
private int _selectedComboItem;
private ICommand _command;
private bool _isControlVisible;
public MainSubViewModel()
{
IsControlVisible = true;
}
public ICommand Command
{
get { return _command ?? (_command = new RelayCommand(CommandMethod)); }
}
private void CommandMethod()
{
if (SelectedComboItem == 8)
IsControlVisible = false;
}
public bool IsControlVisible
{
get { return _isControlVisible; }
set
{
_isControlVisible = value;
OnPropertyChanged();
}
}
public int SelectedComboItem
{
get { return _selectedComboItem; }
set
{
_selectedComboItem = value;
OnPropertyChanged();
}
}
}
4. SecondSubControl:
<UserControl x:Class="SoDataGridProjectsHelpAttempt.SubSubControl"
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>
<Image Source="2014_8_27_Bing_en-AU.jpg" Margin="50"></Image>
</Grid>
5. App.xaml (put into Application.Resources):
<BooleanToVisibilityConverter x:Key="Bol2VisibilityConverter" />
regards,
your problem is that et goes out of scope, so after the function is doesn't exist anymore. you need to create it "outside" in a scope where it will still exist before and after that function.
So for example if there's a class called Application and that class holds the current control that your Button_click is attached to, you need to create the other control there too. Or at least the et variable if you don't want to create the EightTiles until you need it.

WPF: Can't access the view. Datagrid is not refreshing with data in tabs

I'm following this How to create tab-able content in WPF/C#? but I want each tab to show a datagrid. The datagrid doesn't show and also doesn't show the data. When I step into the code, I do see 0,1 being set.
MainWindow.xaml
<Window x:Class="MVVMDataInstances.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewModel="clr-namespace:MVVMDataInstances"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<viewModel:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}"/>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
</Window>
MainWindowViewModel.cs
public ObservableCollection<ChildViewModel> Items { get; private set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ChildViewModel> {new ChildViewModel(0), new ChildViewModel(1)};
}
ChildView.xaml
<UserControl x:Class="MVVMDataInstances.View"
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:ViewModel="clr-namespace:MVVMDataInstances"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<ViewModel:ChildViewModel/>
</UserControl.DataContext>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding Grid, Mode=TwoWay}" />
</UserControl>
ChildViewModel.cs
public class ChildViewModel : ViewModelBase
{
private ObservableCollection<ChildModel> _grid;
public ObservableCollection<ChildModel> Grid
{
get { return _grid; }
private set
{
_grid = value;
OnPropertyChanged("Grid");
}
}
public ChildModel Data { get; set; }
public ChildViewModel()
{
}
public ChildViewModel(int tabNumber)
{
Data = new ChildModel {A = tabNumber.ToString(CultureInfo.InvariantCulture)};
Grid = new ObservableCollection<ChildModel> {Data};
}
}
ChildModel.cs
public class ChildModel
{
public string A { get; set; }
public string B { get; set; }
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I would like to see one grid per tab. The entry on the first tab has a value of 0 for property A. The entry of the second tab has a value of 1 for the property B.
I see that when OnPropertyChanged is called PropertyChanged is null.
I can access the datagrid if I have this in MainWindow.xaml
<Grid>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="True"
ItemsSource="{Binding Grid}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
But OnPropertyChanged is always null for this and I don't see the grid
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:ChildView />
</DataTemplate>
Your DataTemplate is empty. From your question, you probably want to do something along these lines:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" TargetType="{x:Type viewModel:ChildViewModel}">
<DataGrid AutoGenerateColumns="False" . . .>
<DataGrid.Columns>
<!-- Your column definitions here -->
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</Grid.Resources>
<TabControl ContentTemplate="{StaticResource ContentTemplate}"
ItemsSource="{Binding Items}" />
</Grid>
This DataTemplate tells the ChildViewModel.cs that it's appearance is View.xaml :
<DataTemplate x:Key="ContentTemplate"
DataType="{x:Type viewModel:ChildViewModel}">
<viewModel:View />
</DataTemplate>
As a result behind the scenes it also sets each View's DataContext to an instance of ChildViewModel.
I follow this MVVM: ViewModel inheritance to have ViewModel Inheritance. The reason PropertyChanged was null because the constructor was initialized twice. First time with an integer, and then second time with the InitializedComponent. If I comment out the InitializedComponent, the grid was never initialized. The answer to the other stackoverflow question makes everything cleaner with view model inheritance.

Categories

Resources