Caliburn Micro User Control Data Context - c#

I'm new to Caliburn Micro so I'm sure there's something easy that I' missing here.
I have a top-level Shell View:
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:LotRunPlotGrid.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<local:LotRunPlotGridView />
</Grid>
</Window>
and it's associated view model:
namespace LotRunPlotGrid
{
public class ShellViewModel : IShell {}
}
Then I have a user control defined as:
<UserControl x:Class="LotRunPlotGrid.Views.LotRunPlotGridView"
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:converter="clr-namespace:LotRunPlotGrid.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:LotRunPlotGrid.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="900"
d:DataContext="{d:DesignInstance Type=vm:LotRunPlotGridViewModel, IsDesignTimeCreatable=True}">
<UserControl.Resources>
<converter:LotRunItemValueToColorConverter x:Key="ColorConverter"/>
<Style x:Key="LotRunButtonStyle" TargetType="Button">
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Content" Value="{Binding LotID}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row ="0" Text="Lot Run Plot Grid View" FontSize="20" FontFamily="Segoe UI"/>
<ItemsControl Grid.Row="1" ItemsSource="{Binding LotRunItemsCollection}" Margin="0,0,-200,0" HorizontalAlignment="Left" Width="893">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="LotRunItemButton" Style="{StaticResource LotRunButtonStyle}">
<Button.Background>
<MultiBinding Converter="{StaticResource ColorConverter}">
<Binding Path="LotRunDataDisplayMode" />
<Binding Path="LotRunItemValue"/>
</MultiBinding>
</Button.Background>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
and it's associated view model ....
using Caliburn.Micro;
using System.Collections.ObjectModel;
namespace LotRunPlotGrid.ViewModels
{
public class LotRunPlotGridViewModel : PropertyChangedBase
{
private ObservableCollection<LotRunItem> _lotRunItemsCollection = new ObservableCollection<LotRunItem>();
public ObservableCollection<LotRunItem> LotRunItemsCollection
{
get { return _lotRunItemsCollection; }
set { _lotRunItemsCollection = value; }
}
private int _numDisplayedColumns;
public int NumDisplayedColumns
{
get { return _numDisplayedColumns; }
set { _numDisplayedColumns = value; }
}
private int _numDisplayedRows;
public int NumDisplayedRows
{
get { return _numDisplayedRows; }
set { _numDisplayedRows = value; }
}
private int _lotRunDataDisplayMode;
public int LotRunDataDisplayMode
{
get { return _lotRunDataDisplayMode; }
set { _lotRunDataDisplayMode = value; }
}
public LotRunPlotGridViewModel()
{
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot1", LotRunItemValue = "55", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot2", LotRunItemValue = "45", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot3", LotRunItemValue = "35", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot4", LotRunItemValue = "25", LotRunItemColor = "#FF05579" });
LotRunItemsCollection.Add(new LotRunItem() { LotId = "Lot5", LotRunItemValue = "15", LotRunItemColor = "#FF05579" });
}
}
}
The issue I'm having is that the Items Control does not show up because I get a binding error stating that the LotRunItemsCollection is not found in the ShellViewModel, when as displayed above, LotRunItemsCollection is a member of the LotRunPlotGridViewModel.
So what am I missing here regarding binding the LotRunPlotGridViewModel to the LotRunPlotGridView so that LotRunItemsCollection is found in the correct view model?
Thanks for any help!

You get that message because the control has no backing data context with that property name.
Create a property in the shell view model like below
namespace LotRunPlotGrid
{
public class ShellViewModel : PropertyChangedBase, IShell {
private LotRunPlotGridViewModel myGrid = new LotRunPlotGridViewModel();
public LotRunPlotGridViewModel MyGrid {
get { return myGrid; }
set {
myGrid = value;
NotifyOfPropertyChanged();
}
}
}
}
and then in the view you can name the user control to match the property name
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:LotRunPlotGrid.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<local:LotRunPlotGridView x:Name="MyGrid" />
</Grid>
</Window>
The framework will by convention Bind the MyGrid property to the local:LotRunPlotGridView as its data context.
If you do not want to tie the user control directly to the shell the framework is smart enough to look for the view based on the bound view model.
For example if the shell has the following
<Window x:Class="LotRunPlotGrid.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<ContentControl x:Name="MyGrid" />
</Grid>
</Window>
Note the local namespace was removed. The framework when binding the control will notice that the content is empty and search for the matching view of the bound property using naming conventions.

Related

AvaloniaUI - VIewModel can't be bound to Usercontrol

I'm currently working on an application with AvaloniaUI and C#.net. My application has a MainWindow that uses one ViewModel(called MainWindowViewModel) and the Window also contains two UserControls that are integrated via TabControl.
So the issue that I now have is, that I want give each UserControl its own ViewModel, so far I have created a ViewModel for one of my UserControls and also set the namespace to it in the axaml-File of my Control. I've set the DataContext also, but the ViewModel is never been loaded.
As follows here's the source code of my MainWindow, the UserControl und the ViewModel of my UserControl:
MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views ="clr-namespace:MyApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.MainWindow"
WindowStartupLocation="CenterScreen"
Icon="/Assets/Programmicon.png"
Title="{Binding WindowTitle}" CanResize="False" >
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Window.Styles>
<Style Selector="TabItem">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Height" Value="34"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Background" Value="#148198"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
<Style Selector="TabControl WrapPanel">
<Setter Property="Background" Value="#148198"/>
</Style>
<Style Selector="TabItem:selected">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Margin" Value="0 0 0 0"/>
<Setter Property="Padding" Value="10 0"/>
</Style>
</Window.Styles>
<Grid>
<TabControl Name="tabMenu" Background="White">
<TabItem Header="Import" VerticalContentAlignment="Center" >
<views:ImportView/>
</TabItem>
<TabItem Header="Einstellungen" VerticalContentAlignment="Center">
<views:SettingsView/>
</TabItem>
</TabControl>
<Label Name="lblErrorInfo" Content="" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ZIndex="10" Background="Red" Foreground="White" FontSize="34" FontWeight="Bold" IsVisible="false"></Label>
</Grid>
</Window>
SettingsView.axaml
<UserControl xmlns="https://github.com/avaloniaui"
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="using:MyApp.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Views.SettingsView">
<this.DataContext>
<vm:SettingsViewModel/>
</this.DataContext>
<DataGrid Name="gEventlog" Items="{Binding EventlogData}" AutoGenerateColumns="False" CanUserResizeColumns="False" CanUserReorderColumns="False" Background="LightGray" CanUserSortColumns="True" Canvas.Top="20" Width="1024" Height="626" GridLinesVisibility="All" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" AlternatingRowBackground="Azure">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"/>
<DataGridTextColumn Header="Zeitstempel"/>
<DataGridTextColumn Header="Event-Typ"/>
<DataGridTextColumn Header="Benutzer"/>
<DataGridTextColumn Width="563" Header="Fehlermeldung"/>
<DataGridTextColumn Header="Funktion"/>
</DataGrid.Columns>
</DataGrid>
</UserControl>
SettingsViewModel.cs
using MyApp.Classes;
using Microsoft.Data.Sqlite;
using System;
using System.Data;
namespace MyApp.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
// The appconfig class
private readonly AppConfiguration _appConfig;
// The utils class
private readonly Utils _utils;
// The eventlog class
private readonly Eventlog _event;
private string _importFilesPath;
// The data of the eventlog-grid
private DataView _eventlogData;
public DataView EventlogData
{
get { return _eventlogData; }
set
{
if (_eventlogData == value)
{
return;
}
_eventlogData = value;
}
}
public string ImportFilesPath
{
get { return _importFilesPath; }
set
{
if (_importFilesPath == value)
{
return;
}
_importFilesPath = value;
}
}
public SettingsViewModel()
{
// Initialize the members
_appConfig = new AppConfiguration();
_utils = new Utils();
_event = new Eventlog();
_eventlogData = new DataView();
_importFilesPath = "";
this.InitializeGUI();
}
private void InitializeGUI()
{
//Fill the eventlog grid
LoadEventlog();
_importFilesPath = _appConfig.ImportPath;
}
}
}
So my question is, how can I connect my UserControl with the corresponding ViewModel?
I'm new to AvaloniaUI, but coming from WPF where that approach (Give every UserControl an own ViewModel) works. Maybe I am overseeing some essential things.
Thanks in advance for every answer
If you have created your application using MVVM Avalonia template you should have a file called ViewLocator which already "connects" the view model with the corresponding view. Then if you want to display your settings in the main window you can add a property to the MainWindowViewModel:
public SettingsViewModel Settings { get; }
And bind it in the MainWindow
<ContentControl Content="{Binding Settings}"/>

How can I bind my list Property of the object to combo box submenus?

I have this object of class type HouseInfo that contains a list property:
public class HouseInfo
{
public string House
{
get;
set;
}
public List<String> Details
{
get;
set;
}
}
public List<HouseInfo> HouseInfos { get; set; }
I am successfully binding the House property to main items of combo box using ItemSource property in xaml but can't figure out the binding of Details to their respective submenus.
<ComboBox x:Name="Houses1"
Grid.Row="1"
Grid.Column="4"
ItemsSource="{Binding HouseInfos}"
Padding="0"
DisplayMemberPath="House"
VerticalContentAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Stretch"
Margin="0,0,0,2">
</ComboBox>
I tried customizing menuitems in xaml but I get the error "itemsCollection must be empty before using items Source."
How do I get the Details list in each menu item as submenu items?
Any help would be appreciated. Thanks in advance.
Update:
I have bound submenu items as well but they are not visible. I am sure they have bound successfully as it generates submenu items equal to the count of the list inside the details property list of the object. This is the updated xaml for the menu:
<Menu x:Name="menu"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="4"
Height="19">
<MenuItem ItemsSource="{Binding HouseInfos}"
Padding="0"
Background="#0068FF11"
VerticalAlignment="Top"
RenderTransformOrigin="0.5,0.5"
Height="19"
Width="105">
<MenuItem.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="0.5" />
</TransformGroup>
</MenuItem.RenderTransform>
<MenuItem.Header>
<Label x:Name="headerYears"
Margin="0"
Padding="0"
Content="Houses"
Background="#00FF0000"
MaxHeight="18"
UseLayoutRounding="False"
RenderTransformOrigin="0,0"
HorizontalContentAlignment="Center" />
</MenuItem.Header>
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding House}" />
<Setter Property="ItemsSource"
Value="{Binding InfoPoints}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Here is the image of menu which is populated but not visible.
Bound but invisible submenu items
Try using the DataSource property of the combobox. You can assign HouseInfos.House1.
What I did was I dynamically assign them to the combobox
comboBox1.DataSource = HouseInfo.House1.Details;
comboBox1.DisplayMember = "HouseDetails";
comboBox1.ValueMember = "HouseDetailsID";
Or you can try something like the above.
Use this structure. I matched the names with your own names.
MainWindw.xaml
<Window x:Class="MyNameSpace.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:MyNameSpace"
mc:Ignorable="d"
Title="TestMenu" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:HouseInfo}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding House}"/>
</HierarchicalDataTemplate>
</Menu.ItemTemplate>
</Menu>
<Grid>
</Grid>
</DockPanel>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace MyNameSpace
{
/// <summary>
/// Interaction logic for MainWindw.xaml
/// </summary>
public partial class MainWindw : Window
{
public List<HouseInfo> MenuItems { get; set; }
public MainWindw()
{
InitializeComponent();
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2" } };
HouseInfo houseInfo2 = new HouseInfo();
houseInfo2.House = "Header B";
houseInfo2.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header B1" }, new HouseInfo() { House = "Header B2" } };
MenuItems.Add(houseInfo1);
MenuItems.Add(houseInfo2);
DataContext = this;
}
}
public class HouseInfo
{
public string House
{
get;
set;
}
public List<HouseInfo> Details { get; set; }
private readonly ICommand _command;
public HouseInfo()
{
_command = new CommandViewModel(Execute);
}
public ICommand Command
{
get
{
return _command;
}
}
private void Execute()
{
// (NOTE: In a view model, you normally should not use MessageBox.Show()).
MessageBox.Show("Clicked at " + House);
}
}
public class CommandViewModel : ICommand
{
private readonly Action _action;
public CommandViewModel(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
public bool CanExecute(object o)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
}
you can gave style to every element with this code
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</Menu.ItemContainerStyle>
for example add this line to HouseInfo class
public Thickness Margin { get; set; }
and MainWindow.cs
MenuItems = new List<HouseInfo>();
HouseInfo houseInfo1 = new HouseInfo();
houseInfo1.House = "Header A";
houseInfo1.Margin = new Thickness(5);
houseInfo1.Details = new List<HouseInfo>() { new HouseInfo() { House = "Header A1" }, new HouseInfo() { House = "Header A2", Margin=new Thickness(10) } };
and set Style in xaml
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Margin" Value="{Binding Margin}" />
</Style>
</Menu.ItemContainerStyle>
test:

wpf bind to selected listview item and update another list based on that selection

I'm using Visual Studio 2015, I'm trying to teach myself the MVVM pattern, and I'm hitting a road block. My code is loosely based off of Josh Smiths article, I'm using it to help me learn MVVM and create a small app for work in the process.
What I'm trying to accomplish:
I've bound a viewmodel to a listview showing a list of products, each product has a list of "productTemplate" items. In my View I would like this list to populate inside a listbox when a product from my list view is selected. I am implementing INotifyPropertyChanged. I think I'm just missing something simple but I'm not sure.
My code:
Two Models (Product, ProductTemplateItem);
public class Product {
private string _productNum;
private string _productFamily;
public Product() {
}
public string ProductNum { get; set; }
public string ProductFamily { get; set; }
}
public class ProductTemplateItem : ChangeEventHandlerBase {
private string _TemplateItem;
private string _TemplateCode;
public ProductTemplateItem(string templateItem, string templateCode) {
_TemplateItem = templateItem;
_TemplateCode = templateCode;
}
public string TemplateItem {
get { return _TemplateItem; }
set { if(_TemplateItem != value) {
_TemplateItem = value;
OnPropertyChanged("TemplateItem");
}
}
}
public string TemplateCode {
get { return _TemplateCode; }
set {
if (_TemplateCode != value) {
_TemplateCode = value;
OnPropertyChanged("TemplateCode");
}
}
}
public override string DisplayName {
get {
return $"{TemplateItem} - {TemplateCode}";
}
}
}
My ViewModels (Product View Model, brings everything together and adds the product template list, and AlProductsViewModel adds data and exposes everything to be bound in XAML):
public class ProductViewModel : BaseViewModel {
private Product _product;
private bool _isSelected;
private List<ProductTemplateItem> _productTemplate;
public ProductViewModel(string productNum, string productFamily) {
Product.ProductNum = productNum;
Product.ProductFamily = productFamily;
_productTemplate = new List<ProductTemplateItem>();
}
public string ProductNumber {
get { return _product.ProductNum; }
set { if(_product.ProductNum != value) {
_product.ProductNum = value;
OnPropertyChanged("ProductNumber");
}
}
}
public string ProductFamily {
get { return _product.ProductFamily; }
set {
if (_product.ProductFamily != value) {
_product.ProductFamily = value;
OnPropertyChanged("ProductFamily");
}
}
}
public bool IsSelected {
get { return _isSelected; }
set {
if (_isSelected != value) {
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public List<ProductTemplateItem> ProductTemplate {
get { return _productTemplate; }
set { if (_productTemplate != value) {
_productTemplate = value;
OnPropertyChanged("ProductTemplate");
}
}
}
public Product Product {
get {
if (_product == null) {
_product = new Product();
return _product;
}
else {
return _product;
}
}
set { if(_product != value) {
_product = value;
OnPropertyChanged("Product");
}
}
}
public override string DisplayName {
get {
return Product.ProductNum;
}
}
}
public class AllProductsViewModel : BaseViewModel{
public AllProductsViewModel() {
AddProducts();
}
private void AddProducts() {
List<ProductViewModel> all = new List<ProductViewModel>();
all.Add(new ProductViewModel("4835", "Crop Cart"));
all.Add(new ProductViewModel("780", "Piler"));
all.Add(new ProductViewModel("880", "Piler"));
all.Add(new ProductViewModel("150", "Scooper"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Miscellaneous","MISC"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Drawbar", "DRBR"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Mainframe", "FRAM"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Conveyor", "CONV"));
all[1].ProductTemplate.Add(new Model.ProductTemplateItem("Hello", "HELL"));
AllProducts = new ObservableCollection<ProductViewModel>(all);
}
public ObservableCollection<ProductViewModel> AllProducts { get; private set; }
}
And my XAML code which is a user control with a ListView based off of Josh's code and a listbox that needs to be updated based off of the selection in the ListView:
<UserControl x:Class="Parts_Book_Tool.Views.ProductsView"
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:Parts_Book_Tool.Views"
xmlns:viewModel="clr-namespace:Parts_Book_Tool.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModel:AllProductsViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="ProductGroups" Source="{Binding Path=AllProducts}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProductFamily"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProductFamily" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<GroupStyle x:Key="ProductGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
FontWeight="Bold"
Margin="1"
Padding="4,2,0,2"
Text="{Binding Path=Name}"
/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="MainHCCStyle" TargetType="{x:Type HeaderedContentControl}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border
Background="{StaticResource Brush_HeaderBackground}"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="5"
Margin="4"
Padding="4"
SnapsToDevicePixels="True"
>
<TextBlock
FontSize="14"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
Text="{TemplateBinding Content}"
/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ProductItemsStyle" TargetType="{x:Type ListViewItem}">
<!--
Stretch the content of each cell so that we can
right-align text in the Total Sales column.
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a CustomerViewModel object.
-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<HeaderedContentControl Header="Model Info" Style="{StaticResource MainHCCStyle}" Grid.Row="0">
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50" DataContext="{StaticResource ProductGroups}"
ItemContainerStyle="{StaticResource ProductItemsStyle}" ItemsSource="{Binding}" >
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
</HeaderedContentControl>
<HeaderedContentControl Header="Model Template" Style="{StaticResource MainHCCStyle}" Grid.Row="1">
<ListBox ItemsSource="{Binding SelectedItem/ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</HeaderedContentControl>
</Grid>
It feels to me like I'm missing the capture of an event to update the listbox, but with my inexperience in MVVM I Can't be sure. I've tried binding to the SelectedItem of the named element but that doesn't work. I can get the listbox to populate if I bind "AllProducts/ProductTemplate", but it just gives me the first indexed values, and doesn't dynamically change when I select another product.
Hopefully that is enough information, and any help would be greatly appreciated. I'm enjoying learning MVVM but it's dificult to wrap my head around.
Thanks,
Try bind the ListBox to the ProductTemplate property of the SelectedItem property of the ListView:
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50"
ItemsSource="{Binding Source={StaticResource ProductGroups}}"
ItemContainerStyle="{StaticResource ProductItemsStyle}">
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
<ListBox ItemsSource="{Binding SelectedItem.ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

How to draw grid lines that scale?

I have a canvas, and I want to give it grid lines as a background, but I want there to be a constant number of grid lines that divide the canvas into equally-sized sections, rather than just have equally-spaced grid-lines. I want this to be preserved when the canvas is resized by the user.
How should I do this?
Here is a solution which is based in two wpf ListView controls behind the canvas(one for rows and second for columns). The content of the columns related ListView control is a rectangle.
Updated version - Managed Grid Lines Control. Here you can manage the number of grid lines and their visibility.
Xaml code - grid lines control:
<UserControl x:Class="CAnvasWithGrid.GridLineControl"
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:canvasWithGrid="clr-namespace:CAnvasWithGrid"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="This">
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<Style TargetType="ListView">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent"/>
</Style>
<DataTemplate x:Key="InnerListviewDataTemplate" DataType="{x:Type canvasWithGrid:CellModel}">
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" StrokeDashArray="4" Stroke="Black" StrokeThickness="0.5" Fill="Transparent"/>
</DataTemplate>
<DataTemplate x:Key="ListviewDataTemplate" DataType="{x:Type canvasWithGrid:RowModel}">
<ListView ItemsSource="{Binding CellModels}" BorderBrush="#00FFFFFF" BorderThickness="0" Margin="0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding CellModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"></UniformGrid>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="0"
ContentTemplate="{StaticResource InnerListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource InnerListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</DataTemplate>
</Grid.Resources>
<ListView ItemsSource="{Binding ElementName=This, Path=RowModels}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding ElementName=This, Path=RowModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
<Setter Property="Margin" Value="0"></Setter>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter Content="{TemplateBinding Content}" Margin="-1"
ContentTemplate="{StaticResource ListviewDataTemplate}" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate" Value="{StaticResource ListviewDataTemplate}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
Grid lines control - code behind
/// <summary>
/// Interaction logic for GridLineControl.xaml
/// </summary>
public partial class GridLineControl : UserControl
{
public GridLineControl()
{
InitializeComponent();
}
public static readonly DependencyProperty NumberOfColumnsProperty = DependencyProperty.Register(
"NumberOfColumns", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfColumnsChangedCallback));
private static void NumberOfColumnsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)dependencyObject.GetValue(NumberOfRowsProperty);
var numberOfColumns = (int)args.NewValue;
if (numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
public int NumberOfColumns
{
get { return (int) GetValue(NumberOfColumnsProperty); }
set { SetValue(NumberOfColumnsProperty, value); }
}
public static readonly DependencyProperty NumberOfRowsProperty = DependencyProperty.Register(
"NumberOfRows", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfRowsChangedCallback));
private static void NumberOfRowsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var numberOfRows = (int)args.NewValue;
var numberOfColumns = (int)dependencyObject.GetValue(NumberOfColumnsProperty);
if(numberOfColumns == 0 || numberOfRows == 0) return;
var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
}
private static ObservableCollection<RowModel> GetRowModelsCollection(int numberOfRows, int numberOfColumns)
{
var rowModelsCollection = new ObservableCollection<RowModel>();
for (var i = 0; i < numberOfRows; i++)
{
rowModelsCollection.Add(new RowModel(numberOfColumns) {Position = (i + 1).ToString()});
}
return rowModelsCollection;
}
public int NumberOfRows
{
get { return (int) GetValue(NumberOfRowsProperty); }
set { SetValue(NumberOfRowsProperty, value); }
}
public static readonly DependencyProperty RowModelsProperty = DependencyProperty.Register("RowModels",
typeof(ObservableCollection<RowModel>), typeof(GridLineControl),
new PropertyMetadata(default(ObservableCollection<RowModel>)));
public ObservableCollection<RowModel> RowModels
{
get { return (ObservableCollection<RowModel>)GetValue(RowModelsProperty); }
private set { SetValue(RowModelsProperty, value); }
}
}
Models:
public class RowModel:BaseGridMember
{
public RowModel(int numberOfCellsInRow)
{
CellModels = new ObservableCollection<CellModel>();
for (int i = 0; i < numberOfCellsInRow; i++)
{
CellModels.Add(new CellModel{Position = (i+1).ToString()});
}
}
public ObservableCollection<CellModel> CellModels { get; set; }
}
public class CellModel:BaseGridMember
{
}
public class BaseGridMember:BaseObservableObject
{
private string _position;
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged();
}
}
}
Main window xaml code - as you can see here is a ImageContol instead of Canvas but you can replace it:
<Window x:Class="CAnvasWithGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
Title="MainWindow" Height="525" Width="525" x:Name="This">
<Grid Tag="{Binding ElementName=This}">
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="Bool2VisConvKey" />
</Grid.Resources>
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Grid Lines" Command="{Binding ShowGridLinesCommand}"/>
</ContextMenu>
</Grid.ContextMenu>
<Image Source="Resources/Koala.jpg" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseDown="UIElement_OnMouseDown"/>
<canvasWithGrid:GridLineControl NumberOfRows="50" NumberOfColumns="50"
IsHitTestVisible="False" Visibility="{Binding ElementName=This, Path=AreGridLineVisible, Converter={StaticResource Bool2VisConvKey}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Main window code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ShowGridLinesCommand = new RelayCommand(ShowGridLineManageCommand);
AreGridLineVisible = true;
}
private void ShowGridLineManageCommand()
{
AreGridLineVisible = !AreGridLineVisible;
}
public static readonly DependencyProperty AreGridLineVisibleProperty = DependencyProperty.Register(
"AreGridLineVisible", typeof (bool), typeof (MainWindow), new PropertyMetadata(default(bool)));
public bool AreGridLineVisible
{
get { return (bool) GetValue(AreGridLineVisibleProperty); }
set { SetValue(AreGridLineVisibleProperty, value); }
}
public static readonly DependencyProperty ShowGridLinesCommandProperty = DependencyProperty.Register(
"ShowGridLinesCommand", typeof (ICommand), typeof (MainWindow), new PropertyMetadata(default(ICommand)));
public ICommand ShowGridLinesCommand
{
get { return (ICommand) GetValue(ShowGridLinesCommandProperty); }
set { SetValue(ShowGridLinesCommandProperty, value); }
}
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
}
}
How it looks like:
Sounds like a candidate for a custom control with custom drawing. You don't really want to use multiple FrameworkElements like "Line" if you are expecting many grid-lines for performance reasons.
So you'd create a customControl GridLinesControl and overwrite the OnRender method. You can get the actual width and height of the component using the properties ActualWidth and ActualHeight, divide by the number of grid lines you want and draw lines using drawingContext.DrawLine.
The easiest way would be to add the GridLinesControl you've made underneath the canvas, taking up the same space (so it has the right ActualWidth and ActualHeight) like this:
<Grid>
<myControls:GridLinesControl/>
<Canvas ... />
</Grid>
So it's always underneath.

Updating a ListBox with different Content On Button Clicks in WPF

So I have a listbox and a tool bar in my WPF app. The tool bar just has regular controls, and the listbox has vertical expanders.
I need the listbox to have a different set of expanders depending on what button is clicked. Right now it looks like such:
<ListBox>
<local:Select_Analysis_Panel/>
</ListBox>
Where local:Select_Analysis_Panel is seperate user control file containing the expanders. What is the best way to go about dynamically updating the ListBox control's content upon a button click?
For the last couple hours I've been trying to use set DataTemplates for each expander set and bind the to the items control property with little avail with the code below. I'm just trying to get basic framework laid out before setting up a MVVM interface. Later on I was going to replace the ItemsSource="Network_anal" with you know ItemsSource="{Binding WhatExpanderViewModelProperty}" or something like that.
<ListBox Width="250" Margin="5,0,0,0">
<ListBox.Resources>
<DataTemplate DataType="Select_Analysis_Panel">
<local:Select_Analysis_Panel/>
</DataTemplate>
<DataTemplate x:Key="Network_anal" DataType="NetworkAnalysis">
<local:NetworkAnalysis/>
</DataTemplate>.Resources>
<ListBox.Template>
<ControlTemplate>
<Border Background="Red"/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl ItemsSource="Network_anal"/>
</ListBox>
Am I taking the right approach to this at all?
Here's what I'm trying to do. Below when the "File" button is clicked the side bar displays these 2 expanders:
And when "Network Design" button these expanders are dipslayed:
Option 1:
Subclassing the sections:
each of these sections could be subclassed from a base section class and a specific DataTemplate could be used for each:
<Window x:Class="MiscSamples.MultiToolbar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MultiToolbar" Height="300" Width="300">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Sections}"
SelectedItem="{Binding SelectedSection}"
DisplayMemberPath="Name"
DockPanel.Dock="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderBrush="Black" BorderThickness="1">
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter ContentSource="Content"/>
</ToggleButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ScrollViewer Width="300" DockPanel.Dock="Left">
<ContentPresenter Content="{Binding SelectedSection}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:FileSection}">
<TextBlock Text="User Control For File Section"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:NetworkDesignSection}">
<TextBlock Text="User Control For Network Design"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectAnalysisSection}">
<TextBlock Text="User Control For Select Analysis"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</ScrollViewer>
<Grid Background="Gray">
<TextBlock Text="Design Surface" TextAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class MultiToolbar : Window
{
public MultiToolbar()
{
InitializeComponent();
var vm = new MainViewModel();
vm.Sections.Add(new FileSection() {Name = "File"});
vm.Sections.Add(new NetworkDesignSection() { Name = "Network Design" });
vm.Sections.Add(new SelectAnalysisSection() { Name = "Select Analysis" });
DataContext = vm;
}
}
Main ViewModel:
public class MainViewModel: PropertyChangedBase
{
private ObservableCollection<Section> _sections;
public ObservableCollection<Section> Sections
{
get { return _sections ?? (_sections = new ObservableCollection<Section>()); }
}
private Section _selectedSection;
public Section SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
OnPropertyChanged("SelectedSection");
}
}
}
Sections:
public abstract class Section:PropertyChangedBase
{
public string Name { get; set; }
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//Optionally
//public string ImageSource {get;set;}
//ImageSource = "/Resources/MySection.png";
}
public class FileSection: Section
{
///... Custom logic specific to this Section
}
public class NetworkDesignSection:Section
{
///... Custom logic specific to this Section
}
public class SelectAnalysisSection: Section
{
///... Custom logic specific to File Section
}
//...etc etc etc
Result:
Notice that I'm using ToggleButtons bound to the ListBoxItem.IsSelected property to simulate a TabControl-like behavior.
You can set the DataContext of the whole form and bind the ItemsSource of the listbox, or set ItemsSource of the listbox to some collection directly.

Categories

Resources