WPF copying controls programmatically - c#

I created an example control that I want to copy as many times as many I will set in code. I want to duplicate entire <ToggleButton> control.
XAML:
<WrapPanel Name="varom">
<ToggleButton Margin="10">
<StackPanel Orientation="Horizontal">
<Label Content="Stop sign" />
<Image Width="16" Source="{Binding appbar_stop}" />
</StackPanel>
</ToggleButton>
<ToggleButton Margin="10">
<StackPanel Orientation="Horizontal">
<Label Content="Stop sign" />
<Image Width="16" Source="{Binding appbar_stop}" />
</StackPanel>
</ToggleButton>
</WrapPanel>
Now I copied one time <ToggleButton> manually, but if I would have just one <ToggleButton> and I want to get second without copying xaml code...
Is it possible to duplicate(copy) <ToggleButton> control using code?
C#:
namespace WpfApplication3
{
public partial class MainWindow : MetroWindow
{
public const int maxButtons = 4; // number of copies for example
public MainWindow()
{
InitializeComponent();
// code add here for example :)
}
}
}

Models:
public class ButtonViewModel
{
public string Caption { get; set; }
}
public class ViewModel
{
public ViewModel()
{
Buttons = new ObservableCollection<ButtonViewModel>
{
new ButtonViewModel { Caption = "Button 1" },
new ButtonViewModel { Caption = "Button 2" },
new ButtonViewModel { Caption = "Button 3" },
};
}
public ObservableCollection<ButtonViewModel> Buttons { get; }
}
XAML:
<ItemsControl ItemsSource="{Binding Buttons}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Margin="10">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Caption}" />
<Image Width="16"/>
</StackPanel>
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

How to create a collection of Border elements in WrapPanel from an ItemsSource in WPF?

I want to repeat the Border element inside the outer WrapPanel:
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal">
<Border>
<WrapPanel>
<TextBlock Text="{Binding FilterName}"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</WrapPanel>
This is the depending class:
public class FilterField
{
public String FilterName { get; set; }
}
I have a collection of FilterField objects, which should result in:
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal">
<Border>
<WrapPanel>
<TextBlock Text="Test_1"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
<Border>
<WrapPanel>
<TextBlock Text="Test_2"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
<Border>
<WrapPanel>
<TextBlock Text="Test_3"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</WrapPanel>
How can I achieve this?
You can use the ItemsControl to display a collection of items with a specific template in a specific Panel:
<ItemsControl ItemsSource="{Binding Items}">
<!-- Place all items in a WrapPanel -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Define the look of each item -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpfapp1:FilterField}">
<Border>
<WrapPanel>
<TextBlock Text="{Binding FilterName}"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The DataTemplate you define in ItemsControl.ItemTemplate gets applied to all items in the source collection, and each item gets added in the ItemsControl.ItemsPanel that you define, here a WrapPanel.
This is assuming you have a a collection Items of type List<FilterField> in your view model, like this:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new MyViewModel();
}
}
public class MyViewModel {
public List<FilterField> Items { get; set; } = new List<FilterField> {
new FilterField() { FilterName = "Test_1"},
new FilterField() { FilterName = "Test_2"},
new FilterField() { FilterName = "Test_3"},
};
}
public class FilterField {
public string FilterName { get; set; }
}

ListVIew of ItemsControl: Get the ListViewItem index after selecting one its item from ItemsControl

I have a ListView. Inside of this ListView there is ItemsControl and inside it there is second ItemsControl. Inside of the second ItemsControl there are TextBoxes.
ListView -> ItemsControl -> ItemsControl -> TextBox
Is there any chance that I would be able to get index of ListViewItem, which specific TextBox belongs to after clicking on this TextBox?
For example
I select a ListViewItem on index 0 but then I click on TextBox which belong to ListViewItem on index 2. In that case I would like to change value of SelectedGroupIndex from 0 to 2.
"Hello" strings are just for testing.
Thank you very much.
ViewModel
public class MainWindowViewModel
{
public ObservableCollection<ObservableCollection<ObservableCollection<ListViewString>>> AllTexts { get; set; }
public int SelectedGroupIndex { get; set; }
public ICommand AddGroup { get; private set; }
public ICommand AddColumn { get; private set; }
public ICommand TextBoxSelected { get; private set; }
public MainWindowViewModel()
{
this.AllTexts = new ObservableCollection<ObservableCollection<ObservableCollection<ListViewString>>>();
this.SelectedGroupIndex = -1;
this.AddGroup = new Command(this.AddGroupCommandHandler);
this.AddColumn = new Command(this.AddColumnCommandHandler);
this.TextBoxSelected = new Command(this.TextBoxSelectedCommandHandler);
}
private void AddGroupCommandHandler()
{
var tempColumn = new ObservableCollection<ListViewString>() {
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello") };
var tempGroup = new ObservableCollection<ObservableCollection<ListViewString>>();
tempGroup.Add(tempColumn);
this.AllTexts.Add(new ObservableCollection<ObservableCollection<ListViewString>>(tempGroup));
}
private void AddColumnCommandHandler()
{
if (this.SelectedGroupIndex >= 0 && this.SelectedGroupIndex < this.AllTexts.Count)
{
var tempColumn = new ObservableCollection<ListViewString>() {
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello"),
this.GetListViewString("Hello") };
this.AllTexts[this.SelectedGroupIndex].Add(tempColumn);
}
}
private void TextBoxSelectedCommandHandler()
{
// TODO: Change SelectedItem of ListView
// this.SelectedGroupIndex = ...;
}
private ListViewString GetListViewString(string text)
{
return new ListViewString { Value = text };
}
private string GetTextFromListViewString(ListViewString listViewString)
{
return listViewString.Value;
}
}
/// <summary>
/// Class used to show user Text in ListView.
/// Using this class fixes the issue that ObservableCollection didn't update
/// after user changed values of TextBoxes in GUI.
/// </summary>
public class ListViewString : DependencyObject
{
public string Value
{
get
{
return (string)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ListViewString), new PropertyMetadata(string.Empty));
}
View:
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="vm" />
</ResourceDictionary>
</Window.Resources>
<Grid Margin="10,10,10,10" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="300" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding AllTexts, Source={StaticResource vm}, Mode=TwoWay}"
Background="Blue"
SelectedIndex="{Binding SelectedGroupIndex, Source={StaticResource vm}}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Width="100" Height="40">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding TextBoxSelected, Source={StaticResource vm}}" />
</TextBox.InputBindings>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,20,0,0">
<Button Content="Add Group" Width="120" Height="30"
Command="{Binding AddGroup, Source={StaticResource vm}}" />
<Button Content="Add Column" Margin="20,0,0,0" Width="120" Height="30"
Command="{Binding AddColumn, Source={StaticResource vm}}" />
<TextBlock Width="120" Height="30" FontSize="20" Margin="20,0,0,0"
Text="{Binding SelectedGroupIndex, Source={StaticResource vm}}" />
</StackPanel>
</Grid>
Actually what you need is to pass the DataContext of your TextBox to the command as parameter, so use CommandParameter for it and implement your command with parameter:
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding TextBoxSelected, Source={StaticResource vm}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}"/>
</TextBox.InputBindings>
So you will have an item from your items source collection as command parameter and can find the index of it.

How to check to state of controls created dynamically using itemscontrol after naming them in the itemscontrol wpf C#

For my code, I've created multiple control of the same type (i.e. Checkbox) using itemscontrol bound to a class "MyClass". I've named the checkbox control as "checkControl". Now, as I've multiple checkbox control created in the UI, I want to check their state and differentiate among them. How should I proceed? I'm thinking of using findvisualchild & findvisualparent? But, I don't have any idea how to use it?
<ItemsControl Name="myList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="30,80,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Border x:Name="tempBorder" BorderBrush="LightGray" BorderThickness="2,2,2,2" CornerRadius="4,4,4,4" Margin="0,-30,-60,-30"
Background="LightGray">
<StackPanel Orientation="Horizontal" Background="Transparent" Margin="0">
<StackPanel Background="White" Orientation="Horizontal">
<CheckBox x:Name="checkControl" Margin="7,7,0,0" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<WrapPanel>
<Image Source="/Images/myimage.png" Margin="0,10,0,0"></Image>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<TextBlock Text="{Binding Disk}" FontFamily="roboto" FontSize="14"/>
<Rectangle Width="340" Height="1" Margin="0,5,5,5" Fill="Black"/>
<TextBlock>
<Run Text="Item:" FontFamily="roboto" FontSize="14"/>
<Run Text="{Binding Path=Name, StringFormat=' {0} Jr.'}" FontSize="20" Foreground="Orange"
FontWeight="DemiBold"/>
</TextBlock>
</StackPanel>
</WrapPanel>
</CheckBox>
</StackPanel>
<StackPanel Orientation="Vertical" Background="LightGray" Margin="0" HorizontalAlignment="Center" Width="765"
VerticalAlignment="Center">
<TextBlock Text="Select the Option:" Margin="15,7,0,7" FontFamily="roboto"/>
<ComboBox x:Name="comboControl" Margin="15,5,0,7" Width="750" SelectionChanged="comboControl_SelectionChanged"/>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
For My Backend C# Code:
Class
public class MyClass
{
public string Disk { get; set; }
public string Name { get; set; }
public MyClass()
{
}
public MyClass(string album, string name)
{
this.Disk = album;
this.Name = name;
}
}
In my Xaml.cs
public ObservableCollection<MyClass> StudentDisk { get; set; }
//somecode
StudentDisk.Add(new MyClass("Disk 4 ", "John")); //For populating
//somecode
myList.ItemsSource = StudentDisk;
it is possible to differentiate checkBoxes by their DataContext, which should be unique:
void CheckBox_Checked(object sender, RoutedEventArgs e)
{
var checkbox = (CheckBox)sender;
var item = (MyClass)checkbox.DataContext;
MessageBox.Show(item.Disk + " " + item.Name);
}

Dynamic textbox binding to a list

I am very new to MVVM and I am stuck with data binding. I have a button on my view page which dynamically creates text boxes but I cannot see how I bind these textboxes to my List in ViewModel.
In my view i have:
<Button x:Name="btWebsite" Grid.ColumnSpan="2" Width="50" Height="50" Click="btWebsite_Click" Margin="23,245,259,202">
<StackPanel x:Name="pnWebsiteButton" Orientation="Horizontal">
<Image x:Name="imgWebsite" Source= "Images/webIcon.jpg" Stretch="Fill" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
</Button>
<GroupBox x:Name="grpWebsite" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="73,245,0,0" Grid.ColumnSpan="2" Height="51" Width="170" BorderBrush="{x:Null}" BorderThickness="0">
<ScrollViewer x:Name="pnScrollWebsite" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0,0,0,-6">
<StackPanel x:Name="pnWebsite" Orientation="Vertical" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="1,2,0,0" VerticalAlignment="Top" IsEnabled="True">
</StackPanel>
</ScrollViewer>
</GroupBox>
The code behind the button is:
private void btWebsite_Click(object sender, RoutedEventArgs e)
{
var newTextBox = new TextBox();
newTextBox.Text = "type the website address...";
newTextBox.Foreground = Brushes.Gray;
newTextBox.Width = 150;
newTextBox.Name = "txtWebsite" + iWebsites;
pnWebsite.Children.Add(newTextBox);
pnWebsite.RegisterName(newTextBox.Name, newTextBox);
iWebsites++;
}
In my ViewModel i have:
public List<string> Websites
{
get { return _websites; }
set
{
if (value != _websites)
{
_websites = value;
OnPropertyChanged("Websites");
}
}
}
I am struggling to see how I get the website textboxes into the viewmodel list. Thank you for your help
Delete your event handler from the code-behind: btWebsite_Click.
Modify your xaml like this:
<Button x:Name="btWebsite" Grid.ColumnSpan="2" Width="50" Height="50" Command="{Binding AddNewStringCommand}" Margin="23,245,259,202">
<StackPanel x:Name="pnWebsiteButton" Orientation="Horizontal">
<Image x:Name="imgWebsite" Source= "Images/webIcon.jpg" Stretch="Fill" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
</Button>
<GroupBox x:Name="grpWebsite" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="73,245,0,0" Grid.ColumnSpan="2" Height="51" Width="170" BorderBrush="{x:Null}" BorderThickness="0">
<ScrollViewer x:Name="pnScrollWebsite" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0,0,0,-6">
<StackPanel x:Name="pnWebsite" Orientation="Vertical" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="1,2,0,0" VerticalAlignment="Top" IsEnabled="True">
<ItemsControl ItemsSource="{Binding Websites}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</GroupBox>
You need to modify your ViewModel also:
public ObservableCollection<string> Websites { get; set; }
public ICommand AddNewStringCommand => new RelayCommand(AddNewString);
private void AddNewString()
{
Websites.Add(string.Empty);
}
Instead of RelayCommand you can use any implementation of ICommand. I use for example MVVMLight.
As you see, the main differences:
Instead of handling the Click event, there is a Command Binding in
the button.
Instead of generating new controls from code behind, there is an
ItemsControl that creates one every time when we have a new
element in the collection.
The new TextBox's Text property is bound to the new element.
Update:
I made a mistake, ObservableCollection is not working directly with the TextBox.
It seems you need something in addition:
public class Website
{
public string Name { get; set; }
}
Modify the ViewModel like this:
public ObservableCollection<Website> Websites { get; set; } = new ObservableCollection<Website>();
public ICommand AddNewStringCommand => new RelayCommand(AddNewString);
private void AddNewString()
{
Websites.Add(new Website {Name = "new website"});
}
And the ItemsTemplate like this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel >
<TextBox Text="{Binding Name, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>

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