Rectangle wrapped in button does not trigger Command - c#

For school i have to make a WPF ui in c# the domain code is already made by the teacher.
i have to handle click events on a rectangle, i chose to wrap the rectangle around a button.
here is a part of my xaml where i make the rectangle
<Window x:Class="View.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:ViewModel;assembly=ViewModel"
xmlns:controls="clr-namespace:View.Controls"
mc:Ignorable="d"
x:Name="Picross"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:PiCrossControl x:Name="picrossControl" Margin="0,0,10.2,-0.2" Grid="{Binding Grid}" RowConstraints="{Binding RowConstraints}" ColumnConstraints="{Binding ColumnConstraints}">
<controls:PiCrossControl.SquareTemplate x:Uid="rect">
<DataTemplate>
<Button Command="{Binding Path=DataContext.OnClick, ElementName=Picross}">
<Rectangle IsHitTestVisible="False" Width="40" Height="40" Stroke="Black" Grid.Column="0" StrokeThickness="2">
<Rectangle.Fill>
<Binding Path="Contents.Value">
<Binding.Converter>
<local:SquareConverter Empty="White" Filled="#123456" Unknown="Gray"/>
</Binding.Converter>
</Binding>
</Rectangle.Fill>
</Rectangle>
</Button>
</DataTemplate>
</controls:PiCrossControl.SquareTemplate>
<controls:PiCrossControl.RowConstraintsTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Values}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Width="40" Height="40" Text="{Binding Value}" TextAlignment="Center" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</controls:PiCrossControl.RowConstraintsTemplate>
<controls:PiCrossControl.ColumnConstraintsTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Values}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Width="40" Height="40" Text="{Binding Value}" TextAlignment="Center" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</controls:PiCrossControl.ColumnConstraintsTemplate>
</controls:PiCrossControl>
<Button Grid.Column="1" Width="150" Height="20" Command="{Binding CheckSolution}">
<TextBlock Text="check solution"/>
</Button>
</Grid>
the binding OnClick does not register for some reason.
here is my datacontext of the xaml
public class GameModel
{
private IPlayablePuzzle PlayablePuzzle;
public GameModel()
{
var puzzle = Puzzle.FromRowStrings("xxxxx", "x...x", "x...x", "x...x", "xxxxx");
var facade = new PiCrossFacade();
this.PlayablePuzzle = facade.CreatePlayablePuzzle(puzzle);
//Console.WriteLine(string.Join("\n", PlayablePuzzle.RowConstraints.Items.Select(x=>x.ToString())));
//var vmGrid = this.PlayablePuzzle.Grid.Map(square => new SquareViewModel(square)).Copy();
this.CheckSolution = new CheckSolution(PlayablePuzzle);
this.OnClick = new OnClick();
}
public IGrid<IPlayablePuzzleSquare> Grid => PlayablePuzzle.Grid;
public IEnumerable<IPlayablePuzzleConstraints> RowConstraints => this.PlayablePuzzle.RowConstraints;
public IEnumerable<IPlayablePuzzleConstraints> ColumnConstraints => this.PlayablePuzzle.ColumnConstraints;
public string Test => "Test";
#region Commands
public ICommand CheckSolution { get; }
public ICommand OnClick { get; }
#endregion
}
as you can see i have 2 ICommands checksolution is also bound to a button in my xaml and that button does work (it is not wrapped around a rectangle it is just a button wrapped around a textblock) the ICommand OnClick is the one bound to my rectangle button.
here is my OnClick code
public class OnClick : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Console.WriteLine(parameter);
}
}
update:
i set the name of window to picross and updated my binding on the button and now get the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Picross'. BindingExpression:Path=DataContext.OnClick; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

This happens because the command is being bound from within a DataTemplate. You will need to "walk back" to the parent to find the data context.
Something like this:
Command="{Binding Path=DataContext.OnClick, ElementName=MainWindowName}"
In this example I am assuming you set the GameModel as the DataContext of a Window named "MainWindowName".
So you can add your name to your Window like this:
x:Name="MainWindowName"
The you can assign your data context in your window constructor like this:
public MainWindow()
{
InitializeComponent();
DataContext = new GameModel();
}
This binding can be expressed in other ways.
Another way this can be done is using Reference like this:
Command="{Binding Path=DataContext.OnClick, Source={x:Reference picrossControl}}"

Related

Trouble binding buttons in a DataTemplate to a RelayCommand

I have a view that is displaying a dynamic list of buttons (shown as text.) The users will already expect the text to be "selectable". But that is beside the point.
I am trying to bind the button(s) to a RelayCommand but when I test, clicking a line of text does not cause the bound command to be executed. I'm not sure what I am missing. This is the first time I've tried something like this, using an ItemsControl with a DataTemplate. What am I missing? Here is the view xaml:
<Window x:Class="MyCode.Correction.CorrectionView"
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:ls="clr-namespace:MyCode.Correction"
DataContext="{Binding Source={StaticResource Locator}, Path=CorrectionViewModel, UpdateSourceTrigger=PropertyChanged}"
mc:Ignorable="d"
Width="270"
Height="300"
ResizeMode="NoResize"
Title="Correction menu"
Topmost="True"
WindowStartupLocation="CenterOwner"
Icon="/MyApp;component/Images/cc.ico"
AutomationProperties.AutomationId="CorrectionWindow">
<Grid Margin="0,10,-6,9" RenderTransformOrigin="0.264,0.344">
<Grid.RowDefinitions>
<RowDefinition Height="114*" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding CorrectionOptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding CorrectionCommand, Mode=OneTime}"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}}"
Margin="20,5,0,0"
FontSize="15">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
And here is the viewmodel:
namespace MyCode.Correction
{
public class CorrectionViewModel : DialogViewModelBase
{
private readonly IDialogService _dialogService;
private readonly ILogger Logger;
public CorrectionViewModel(IDialogService dialogService)
{
Logger = LoggerFactory.GetLoggerInstance(typeof(CorrectionViewModel));
CorrectionCommand = new RelayCommand<object>((s) => OnCorrectionClicked(s));
_dialogService = dialogService;
}
public RelayCommand<object> CorrectionCommand { get; set; }
private ObservableCollection<string> _correctionOptions;
public ObservableCollection<string> CorrectionOptions
{
get
{
return _correctionOptions;
}
set
{
Set(() => CorrectionOptions, ref _correctionOptions, value);
}
}
private void OnCorrectionClicked(object selectedCorrection)
{
UserDialogResult = (string)selectedCorrection;
CloseAction();
}
}
}
You command and parameter bindings are wrong.
The Command binding has to point to the data context of the parent ItemsControl, which is the CorrectionViewModel that contains the CorrectionCommand. This is done using RelativeSource.
The CommandParameter is the current data context itself (the clicked correction option string). A TemplatedParent as RelativeSource is used in control templates only, not for data templates.
It should work like this.
<ItemsControl ItemsSource="{Binding CorrectionOptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding DataContext.CorrectionCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
Margin="20,5,0,0"
FontSize="15">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

(WPF) How to change ListBox items/headers font size at runtime?

I want to allow user to change the font size a listbox during runtime. What's the simplest way to achieve that? (also, is there a way to simplify the code in general?)
Here's a xaml for the list I have: https://pastebin.com/Y8q5W50S
<ListBox Grid.Row="1" Grid.Column="1" Name="CardsListBox" ItemsSource="{Binding Path=placement}"
Background="Transparent" BorderBrush="Transparent">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Style="{StaticResource ExpanderStyle}">
<Expander.Header>
<TextBlock Text="{Binding Name}" Style="{StaticResource PlacementHeaderStyle}" />
</Expander.Header>
<ItemsPresenter IsEnabled="False" />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=power}" Style="{StaticResource CardPowerStyle}" />
<TextBlock Grid.Column="2" Text="{Binding Path=name}" Style="{StaticResource CardNameStyle}" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So here is an example: Size of the font inside the item template of the ListBox is binded from ViewModel. The same value is also binded to slider so you can check how it works and change it on runtime.
First, you will have to create the view model where you will have to keep the value of font size. The view model will have to implement INotify property changed.
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
Items = new ObservableCollection<string> {"item1", "item2"};
_fontSize = 12d;
}
public ObservableCollection<string> Items { get; set; }
private double _fontSize;
public double FontSize
{
get => _fontSize;
set
{
_fontSize = value;
OnPropertyChanged(nameof(FontSize));
}
}
#region INotifyPropertyChangedImplementation
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Next step is to set view model as view data context for example like this ( Please, checkout for example, Caliburn Micro or Prism to make this automatically).
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent();
}
}
After that bind the font size to the list using a relative source binding from ListBox item template to window DataContext ( MainWindowViewModel).
Ex:
<Window x:Class="ListBindingWpfApp.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:ListBindingWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Text="Font size slider:"></TextBlock>
<Slider Maximum="30" Minimum="5" Value="{Binding FontSize}"/>
</StackPanel>
<ListBox Grid.Row="1" Name="CardsListBox" ItemsSource="{Binding Path=Items}"
Background="Transparent" BorderBrush="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="{Binding DataContext.FontSize, RelativeSource={RelativeSource AncestorType=Window}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I have attached working project.
Sorry for the not detailed description I will update it later.
Sample Project

Data bind an ItemsControl in a DataTemplate

I seem to have a simple data-binding problem, but can't figure out the right way to do it. There is a TabControl which defines two DataTemplate's, one for the tab header and one for the tab content.
The content template contains an ItemsControl. The ItemsControl tries to bind to a dynamically created ViewModel (ConnectionInfoVM).
When I display the UI, the binding just fails, but there is no error-message in the output about it.
How do I have to set up the DataContext and the binding so the binding works and the DataBuffer is actually displayed? Any help greatly appreciated.
ConnectionsControl:
<UserControl x:Class="XXXViewer.Views.ConnectionsControl"
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:viewModels="clr-namespace:XXXViewer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabControl Grid.Row="0" Name="TabDynamic" SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
</UserControl>
ConnectionsControl code behind:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<TabItem> _tabItems = new ObservableCollection<TabItem>();
public ConnectionsControl()
{
InitializeComponent();
// bindings
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
DataTemplate headerTemplate = TabDynamic.FindResource("TabHeader") as DataTemplate;
DataTemplate contentTemplate = TabDynamic.FindResource("TabContent") as DataTemplate;
// create new tab item
TabItem tab = new TabItem
{
Header = $"Tab {ci.ConnectionID}",
Name = $"T{ci.ConnectionID}",
HeaderTemplate = headerTemplate,
ContentTemplate = contentTemplate,
DataContext = ci
};
_tabItems.Insert(0, tab);
// set the new tab as active tab
TabDynamic.SelectedItem = tab;
}
}
}
ConnectionInfoVM:
namespace XXXViewer.ViewModels
{
public class ConnectionInfoVM : ViewModelBase
{
private readonly ObservableQueue<string> _dataBuffer = new ObservableQueue<string>();
public ObservableQueue<string> DataBuffer => _dataBuffer;
}
}
Screenshot of the tab that gets created:
resulting tab
You set the ContentTemplate but never the Content, so the ContentTemplate is never applied because it's applied only when there's Content set. Instead of DataContext = ci write Content = ci.
By the way the DataContext = ci was useless because the DataContext is already implicitely the object on which the DataTemplate is applied.
Edit
As you're using WPF, use and abuse of its core feature: bindings.
How I would have written your code (if I didn't use full MVVM compliant code):
Your XAML:
<TabControl Grid.Row="0" Name="TabDynamic"
ItemsSource="{Binding TabItems, Mode=OneWay}"
SelectionChanged="tabDynamic_SelectionChanged">
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModels:ConnectionInfoVM">
<TabItem Header="{Binding ConnectionID, Mode=OneWay}"
Name="{Binding ConnectionID, Mode=OneWay}"
HeaderTemplate="{StaticResources TabHeader}">
<StackPanel>
<ScrollViewer Name="Scroller" Background="Black">
<StackPanel>
<TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
<ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</StackPanel>
</TabItem>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You cs code become much simpler:
namespace XXXViewer.Views
{
public partial class ConnectionsControl : UserControl
{
private readonly ObservableCollection<ConnectionInfoVM> _tabItems = new ObservableCollection<ConnectionInfoVM>();
public ObservableCollection<ConnectionInfoVM> TabItems {get {return _tabItems;}}
public ConnectionsControl()
{
InitializeComponent();
// bindings
//TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
}
// assume this gets called
private void AddTabItem(ConnectionInfoVM ci)
{
TabItems.Add(ci);
}
}
}
I noted while re-reading your code that you were probably confused about binding in code-behind.
Your code TabDynamic.ItemsSource = _tabItems; is not a binding, it will only set it once.
Anyway, I suggest you read a bit about MVVM. The TabItems should be in a ViewModel class instead of being in code-behind.
The Tabcontrol as per your coding does not contain in it's DataContext the viewmodel but the control; so we need to find a control or something else which holds the VM. It does not appear that the page holds the VM in its DataContext either.
I recommend that one route is to use the TabControl's Tag property to hold the VM such as specifying it in code behind as such:
TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
TabDynamic.Tag = {Wherever you are keeping your VM at this time its not clear in your code example};
Then you can specify Tag from the template binding by specifying the TabControls' name as such:
<ItemsControl Name="ItemCtrl"
ItemsSource="{Binding Tag.DataBuffer,
ElementName=TabDynamic}">

WPF ListBox bind ItemsSource with MVVM-light

XAML
<Window x:Class="Html5Mapper.Mapper.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpt="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="HTML mapper" Height="300" Width="600" >
<Window.DataContext>
<Binding Path="Main" Source="{StaticResource Locator}"/>
</Window.DataContext>
<ListBox Name="lbFiles" ItemsSource="{Binding Filescollection, Mode=OneWay}" Width="240" Height="220">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Image Source="HTML.png" Height="40" Width="32" />
<TextBlock Grid.Column="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MainViewModel.cs
public ObservableCollection<Files> Filescollection { get; set; }
public MainViewModel()
{
this.Filescollection = new ObservableCollection<Files>();
SelectFilesAction();
}
private void SelectFilesAction()
{
this.Filescollection.Add(new Files { Name = "index.html", Path = "C:\\Files"});
//lbFiles.ItemsSource = docs;
}
Q: What else do I need to get the docs object into the Listbox ?
In my opinion you are binding your controls to wrong datacontect, check output window for erros. You might want to set datacontext of main window to your MainViewModel (in codebehind or similar). Also why do you create another instance for docs? It is useless.
public ObservableCollection<Files> Filescollection {get;set;}
public MainViewModel()
{
this.Filescollection = new ObservableCollection<Files>();
SelectFilesAction();
}
private void SelectFilesAction()
{
Filescollection.Add(new Files { Name = "index.html", Path = "C:\\Files"});
}
Vidas is correct in that the DataContext of your Window is wrong, it needs to be your MainViewModel class.
Get rid of this:
<Window.DataContext>
<Binding Path="Main" Source="{StaticResource Locator}"/>
</Window.DataContext>
And add this to the Window tag:
<Window DataContext="{StaticResource MainViewModel}">
And that should do it.
<UserControl x:Class="RetailPOS.View.Usercontrols.MainWindow.Products"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
xmlns:ctrl="clr-namespace:RetailPOS.View.Usercontrols.MainWindow"
d:DesignHeight="200" d:DesignWidth="490" **DataContext="{Binding Source={StaticResource Locator}, Path=CategoryVM}"**
xmlns:my="clr-namespace:WpfLab.Controls;assembly=WpfLab.Controls">
<UserControl.Resources>
</UserControl.Resources>
<Grid Width="490" Height="360">
<ListBox Name="LstProduct" ItemsSource="{Binding LstProduct}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" Margin="0" Height="Auto" Width="490" >
</WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Button Margin="1" Content="{Binding Name}" Height="53" Background="{Binding Color}" HorizontalAlignment="Right" Width="79"
Command="{Binding DataContext.ShowProductCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}">
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
In the code behind
private ObservableCollection<ProductDTO> _lstProduct;
public ObservableCollection<ProductDTO> LstProduct
{
get { return _lstProduct; }
set
{
_lstProduct = value;
RaisePropertyChanged("LstProduct");
}
}
/// <summary>
/// Get all Commonly Used Products
/// </summary>
/// <returns>returns list of all Commonly Used products present in database</returns>
private void FillCommonProducts()
{
LstProduct = new ObservableCollection<ProductDTO>(from item in ServiceFactory.ServiceClient.GetCommonProduct()
select item);
}

Horizontal orientated WrapPanel within ItemsControl lists vertically

I have two DataTemplates defined within my XAML, each used for a seperate ItemsControl panel.
The main ItemsControl lists Foo objects stored within an ObservableCollection object.
The Foo object itself has its own set of items stored within as an ObservableCollection object.
I tried to define the XAML in a way that allows for each of the ObservableCollection Foo items to be displayed with its name in a header (The first ItemsControl). From this the list within each Foo item itself should be displayed horizontally (Using the second ItemsControl) with a related field directly below. If enough items are present then they should wrap to the next line where necessary.
This is how the UI currently stands:
This is how I wish the UI to actually appear:
My Markup (Button controls are for another aspect of the UI) :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ItemsControl x:Name="ContentList" ItemTemplate="{StaticResource GameTemplate}" Grid.Column="0" />
</ScrollViewer>
<StackPanel Grid.Column="1" Background="DarkGray">
<Button Click="OnLoad">_Load</Button>
<Button Click="OnSave">_Save</Button>
<Button Click="OnAdd">_Add</Button>
<Button Click="OnDelete">_Delete</Button>
</StackPanel>
</Grid>
DataTemplate for listing Foo items:
<DataTemplate x:Key="GameTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="{Binding Name}" Grid.Row="0" Background="Gray" FontSize="16" />
<ItemsControl x:Name="imageContent"
ItemsSource="{Binding FileList}"
ItemTemplate="{StaticResource GameImagesTemplate}"
Grid.Row="1" />
</Grid>
</DataTemplate>
DataTemplate for listing items within each Foo item:
<DataTemplate x:Key="GameImagesTemplate">
<WrapPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" >
<Image Source="{Binding FileInfo.FullName}"
Margin="8,8,8,8"
Height="70"
Width="70" />
<Label Content="{Binding Name}" />
</StackPanel>
</WrapPanel>
</DataTemplate>
I'm fairly new to WPF so I have a feeling it's an issue caused by how I am using the controls.
What WPF changes would I need to make in order to generate the UI I would like?
I think its because you are adding each image item to a new WrapPanel in GameImagesTemplate , you should just have to set the ItemsControl ItemsPanelTemplate to WrapPanel in the GameTemplate
Example:
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="252.351" Width="403.213" Name="UI" >
<Window.Resources>
<DataTemplate x:Key="GameImagesTemplate" >
<StackPanel>
<Image Source="{Binding FileInfo.FullName}" Margin="8,8,8,8" Height="70" Width="70" />
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GameTemplate">
<StackPanel>
<Label Content="{Binding Name}" Grid.Row="0" Background="Gray" FontSize="16" />
<ItemsControl x:Name="imageContent" ItemsSource="{Binding FileList}" ItemTemplate="{StaticResource GameImagesTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding ElementName=UI, Path=FileList}" Grid.Column="0" ItemTemplate="{StaticResource GameTemplate}" />
</ScrollViewer>
</Grid>
</Window>
Code:
public partial class MainWindow : Window
{
private ObservableCollection<Foo> _fileList = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
foreach (var item in Directory.GetDirectories(#"C:\StackOverflow"))
{
FileList.Add(new Foo
{
Name = item,
FileList = new ObservableCollection<Bar>(Directory.GetFiles(item).Select(x => new Bar { FileInfo = new FileInfo(x) }))
});
}
}
public ObservableCollection<Foo> FileList
{
get { return _fileList; }
set { _fileList = value; }
}
}
public class Foo
{
public string Name { get; set; }
public ObservableCollection<Bar> FileList { get; set; }
}
public class Bar
{
public FileInfo FileInfo { get; set; }
}
Result

Categories

Resources