The Problem
I have a C# window with some text fields and buttons on it. It starts out similar to this:
When the user clicks that "+ Add Machine Function" button, I need to create a new row of controls and move the button below those:
If the user clicks "+Add Scale Unit" the program needs to add some controls to the right:
Attempts at a solution
I have tried using Windows Forms' TableLayoutPanel but it seemed to handle resizing itself to fit additional controls in odd ways, for example it would some one rows of controls much wider than the others, and would make some rows so short it cut off parts of my controls.
I have also tried simply placing the controls by themselves into the form by simply calculating their relative positions. However I feel that this is bad programming practice as it makes the layout of the form relatively hard to change later. In the case of the user deleting the row or scale unit by pressing the 'X' beside it, this method also requires the program to find each element below that one and move it up individually which is terribly inefficient.
My question is: how would I go about creating a dynamically growing/shrinking application, either through Windows Forms layouts or WPF or something else?
In WPF you can do this:
Classes
public class MachineFunction
{
public string Name { get; set; }
public int Machines { get; set; }
public ObservableCollection<ScaleUnit> ScaleUnits { get; set; }
public MachineFunction()
{
ScaleUnits = new ObservableCollection<ScaleUnit>();
}
}
public class ScaleUnit
{
public string Name { get; set; }
public int Index { get; set; }
public ScaleUnit(int index)
{
this.Index = index;
}
}
Window.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="350" Width="525">
<StackPanel>
<ItemsControl Name="lstMachineFunctions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="Machine Function"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="Number of Machines"/>
<Button Grid.Row="1" Grid.Column="0" Click="OnDeleteMachineFunction">X</Button>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Machines}"/>
</Grid>
<ItemsControl ItemsSource="{Binding ScaleUnits}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="12,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1" Text="Machine/Scale Unit"/>
<Button Grid.Row="1" Grid.Column="0" Click="OnDeleteScaleUnit">X</Button>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Index, StringFormat='Scale Unit {0}'}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button VerticalAlignment="Center" Click="OnAddScaleUnit">Add Scale Unit</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button HorizontalAlignment="Left" Click="OnAddMachineFunction">Add Machine Function</Button>
</StackPanel>
</Window>
Window.cs
public partial class MainWindow : Window
{
public ObservableCollection<MachineFunction> MachineFunctions { get; set; }
public MainWindow()
{
InitializeComponent();
lstMachineFunctions.ItemsSource = MachineFunctions = new ObservableCollection<MachineFunction>();
}
private void OnDeleteMachineFunction(object sender, RoutedEventArgs e)
{
MachineFunctions.Remove((sender as FrameworkElement).DataContext as MachineFunction);
}
private void OnAddMachineFunction(object sender, RoutedEventArgs e)
{
MachineFunctions.Add(new MachineFunction());
}
private void OnAddScaleUnit(object sender, RoutedEventArgs e)
{
var mf = (sender as FrameworkElement).DataContext as MachineFunction;
mf.ScaleUnits.Add(new ScaleUnit(mf.ScaleUnits.Count));
}
private void OnDeleteScaleUnit(object sender, RoutedEventArgs e)
{
var delScaleUnit = (sender as FrameworkElement).DataContext as ScaleUnit;
var mf = MachineFunctions.FirstOrDefault(_ => _.ScaleUnits.Contains(delScaleUnit));
if( mf != null )
{
mf.ScaleUnits.Remove(delScaleUnit);
foreach (var scaleUnit in mf.ScaleUnits)
{
scaleUnit.Index = mf.ScaleUnits.IndexOf(scaleUnit);
}
}
}
}
I did the same thing recently in WinForms and the way I did it was as follows:
Create a UserControl that contains the controls I wanted to repeat
Add a FlowLayoutPanel to the main form to contain all the user controls (and to simplify their positioning)
Add a new instance of your custom UserControl to the FlowLayoutPanel every time you want a new "row" of controls
flowLayoutPanel1.Controls.Add(
new MachineFunctionUC {
Parent = flowLayoutPanel1
});
To remove a row of control call this.Dispose(); from within the user control (that's the instruction executed by the "X" button).
If you want the UserControls to be arranged vertically set the following properties:
flowLayoutPanel1.AutoScroll = true;
flowLayoutPanel1.WrapContents = false;
flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
And to access them use flowLayoutPanel1.Controls[..]
The correct way to achieve your requirements in WPF is for you to define a custom data type class to represent your machine function. Provide it with how ever many properties that you need to represent your machine fields. When you have done this, you then need to move the code that generated your machine function UI into a DataTemplate for the type of your class and data bind all of the relevant properties:
<DataTemplate DataType="{Binding YourPrefix:MachineFunction}">
...
</DataTemplate>
Then, you need to create a collection property to hold your machine function items and data bind that to some kind of collection control. Once you have done this, then to add another row, you just need to add another item to the collection:
<ItemsControl ItemsSource="{Binding MachineFunctions}">
<ItemsControl.Resources>
<DataTemplate DataType="{Binding YourPrefix:MachineFunction}">
...
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<Button Content="+ Add Machine Function" ... />
...
MachineFunctions.Add(new MachineFunction());
Please see the Data Binding Overview page on MSDN for further help with data binding.
Create a function which will define a row for you. Consider the code and use its where to place another control and do as for buttons also and count it position.
Button button1=new Button();
button1.Text="dynamic button";
button1.Left=10; button1.Top=10; //the button's location
this.Controls.Add(button1); //this is how you can add control
Related
My code has two ListBoxes (LeftListBox and RightListBox) and one TextBox. Those ListBoxes transfer the items between the them. The TextBox shows a number, which corresponds to the SelectedItem in RightListBox (e.g., when T1 is selected, it shows 10, when T2 is selected, it shows 20, and so on).
I've just made it!! ... but those ListBoxes show only the 1st Dictionary item out of three ... How can I show all of them?
Here is my code:
MainWindow.xaml
<Window x:Class="ListBoxMoveAll.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:ListBoxMoveAll"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox x:Name="LeftListBox" Grid.Row="0" Grid.RowSpan="3" Grid.Column="0"
ItemsSource="{Binding LeftListBoxItems}" DisplayMemberPath="Key" SelectedValuePath="Value"
SelectionMode="Extended" Margin="0,10"/>
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center">
<Button Content="Add" x:Name="Add_Button" Click="Add_Button_Click"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="2" VerticalAlignment="Center">
<Button Content="Remove" x:Name="Remove_Button" Click="Remove_Button_Click"/>
</StackPanel>
<ListBox x:Name="RightListBox" Grid.Row="0" Grid.RowSpan="3" Grid.Column="2"
ItemsSource="{Binding RightListBoxItems}" DisplayMemberPath="Key" SelectedValuePath="Value"
SelectionMode="Extended" Margin="0,10"/>
<StackPanel Grid.Column="4" Grid.Row="1" Grid.RowSpan="1" Margin="0,10">
<TextBox Text="{Binding SelectedValue.Step, ElementName=RightListBox}"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using ListBoxMoveAll.Model;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
namespace ListBoxMoveAll
{
public partial class MainWindow : Window
{
public ObservableCollection<Dictionary<string, Graph>> LeftListBoxItems { get; }
= new ObservableCollection<Dictionary<string, Graph>>();
public ObservableCollection<Dictionary<string, Graph>> RightListBoxItems { get; }
= new ObservableCollection<Dictionary<string, Graph>>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
Dictionary<string, Graph> Dic = new Dictionary<string, Graph>();
Dic.Add("T1", new Graph(10));
Dic.Add("T2", new Graph(20));
Dic.Add("T3", new Graph(30));
LeftListBoxItems.Add(Dic);
// Yes! There are three items in Dic!
foreach (var item in Dic)
{
MessageBox.Show(item.ToString());
}
}
private void Add_Button_Click(object sender, RoutedEventArgs e)
{
//foreach (TestModel item in LeftListBox.SelectedItems.OfType<TestModel>().ToList())
foreach (Dictionary<string, Graph> item
in LeftListBox.SelectedItems.OfType<Dictionary<string, Graph>>().ToList())
{
LeftListBoxItems.Remove(item);
RightListBoxItems.Add(item);
}
}
private void Remove_Button_Click(object sender, RoutedEventArgs e)
{
foreach (Dictionary<string, Graph> item
in RightListBox.SelectedItems.OfType<Dictionary<string, Graph>>().ToList())
{
RightListBoxItems.Remove(item);
LeftListBoxItems.Add(item);
}
}
}
}
Model/Graph.cs
namespace ListBoxMoveAll.Model
{
public class Graph
{
public Graph(int step) { Step = step; }
public int Step { get; set; }
}
}
Since the foreach statement shows that there are three items in Dic, I think my XAML has an issue, but I cannot figure that out. You guys might know what the issue is. Sorry if this is a very basic question ... Thank you in advance.
New Discovery: I've added another foreach statement for LeftListBoxItems:
foreach (var item in LeftListBoxItems)
{
MessageBox.Show(item.ToString());
}
... The output is only once and the content is:
System.Collections.Generic.Dictionary`2[System.String,ListBoxMoveAll.Model.Graph]
What does this mean?
Since you're using data binding the "proper" way to handle this is to wrap both the text and item in a view model:
public class GraphViewModel
{
public string Text { get; }
public Graph Graph { get; }
public GraphViewModel(string text, Graph graph) => (Text, Graph) = (text, graph);
}
No need for the dictionary, your collections can use this type instead:
public ObservableCollection<GraphViewModel> LeftListBoxItems { get; } = new ObservableCollection<GraphViewModel>();
public ObservableCollection<GraphViewModel> RightListBoxItems { get; } = new ObservableCollection<GraphViewModel>();
Similarly, there's no point in adding items to a dictionary only to add them to the collection immediately afterwards, just add them to the collection directly:
LeftListBoxItems.Add(new GraphViewModel("T1", new Graph(10)));
LeftListBoxItems.Add(new GraphViewModel("T2", new Graph(20)));
LeftListBoxItems.Add(new GraphViewModel("T3", new Graph(30)));
A minor change to each of your button handlers:
foreach (var item in LeftListBox.SelectedItems.Cast<GraphViewModel>().ToList())
... and ...
foreach (var item in RightListBox.SelectedItems.Cast<GraphViewModel>().ToList())
Finally, bind your list box DisplayMemberPath properties to Text and get rid of the SelectedValuePath:
<ListBox x:Name="LeftListBox" Grid.Row="0" Grid.RowSpan="3" Grid.Column="0"
ItemsSource="{Binding LeftListBoxItems}" DisplayMemberPath="Text"
SelectionMode="Extended" Margin="0,10"/>
<ListBox x:Name="RightListBox" Grid.Row="0" Grid.RowSpan="3" Grid.Column="2"
ItemsSource="{Binding RightListBoxItems}" DisplayMemberPath="Text"
SelectionMode="Extended" Margin="0,10"/>
This is similar to Genos Loser's 2nd example, but a view model is more typesafe than a KeyValuePair and it's also easier to add INPC to later if you need to.
Obviously the LeftListBoxItems only has one item when initializing the MainWindow's constructor, so only shows one item is normal.
So the problem is you should put 3 items in the LeftListBoxItems, not just only once( LeftListBoxItems.Add(Dic); ).
If you want to resolve the problem, perhaps you can change the
ObservableCollection<Dictionary<string, Graph>>
to
ObservableCollection<KeyValuePair<string, Graph>>
, so that you can show 3 items on the app.
Updated
If you want to keep Dictionary type as your collection property, you can refer this link WPF - How can I implement an ObservableCollection<K,T> with a key (like a Dictionary)? to change ObservableCollection type.
I have a screen displaying a list of items on which the user can click a button to remove the corresponding item from the list.
I am trying to do so using MVVM.
But the item is not aware of the containing list when it gets the action.
I saw some answers here and there, but none of them using out of the box MVVM features I have in my environment
For example that one using PRISM (don't know if I should use that too, is it standard?):
How to properly remove Items from a ListView when the ItemTemplate is a User Control?
Here is the XAML:
<ListView ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding RemoveItemCommand}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ModelView list:
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass{ Property01 = "Sometext" }
};
public ObservableCollection<ItemClass> MyItemList { get { return _MyItemList; } }
And I want to be able to perform the following (the example of code from the main model view, I could create an item model view if necessary for solving):
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel(IUserDialogs dialogs)
{
RemoveItemCommand = new MvxCommand(RemoveItem);
}
public void RemoveItem(object theItem) { MyItemList.Remove(theItem); }
Add x:Name="listView" attribute to your ListView, then in the template
<Button Grid.Column="1"
Command="{Binding ElementName=listView, Path=DataContext.RemoveItemCommand}"
CommandParameter="{Binding}" >
However, when I face problems like this, I usually just use code behind instead. The reason for that, I can use debugger for C# code in visual studio, but debugging these complex bindings is much harder. Here’s a C# version, the code is IMO cleaner, and easier to debug:
void removeItem_Click( object sender, RoutedEventArgs e )
{
object i = ((FrameworkElement)sender).DataContext;
( this.DataContext as MyViewModel )?.RemoveItem( i );
}
Or maybe that's just my personal preference.
It would be better to have a context menu item on the list view (or a delete button on the page somewhere) to delete the currently selected item(s). You can then get the selection from the list view.
Alternatively you could attach the context menu to the list view item in PrepareContainterForItemOverride (and detach it in the other Override method)
That would be a more standards interaction style.
If you must have the button inside the list view item, then the easiest way to get the list item would probably be to use a visual tree helper to go up from the button to the list view item and then get the actual item from the list view item.
Thanks for all the hints,
Using Soonts answer, I was able to develop a fast solution,
Here is what the final implementation looks like for reference for whoever wants to copy/paste/adapt (note I did not test code as I replaced variables/functions names):
XAML:
<ListView x:Name="ItemClass_ListView" ItemsSource="{Binding MyItemList}" SelectionMode="None" ScrollViewer.VerticalScrollMode="Disabled" ItemContainerTransitions="{x:Null}">
<ListView.ItemTemplate>
<DataTemplate >
<Grid Grid.Row="1" HorizontalAlignment="Stretch" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding ItemClass.Property01, Mode=TwoWay}" />
<Button Grid.Column="1" Command="{Binding ElementName=ItemClass_ListView, Path=DataContext.RemoveItemCommand}" CommandParameter="{Binding}" >
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public class MyViewModel : BaseViewModel, INotifyPropertyChanged
{
public IMvxCommand RemoveItemCommand { get; private set; }
public MyViewModel()
{
// Initializing Commands
RemoveItemCommand = new MvxCommand<ItemClass>(OnRemoveItemClick);
}
public void OnRemoveItemClick(ItemClass anItem)
{
// Do stuff...
}
private static ObservableCollection<ItemClass> _MyItemList = new ObservableCollection<ItemClass> {
new ItemClass(),
new ItemClass()
};
public ObservableCollection<ItemClass> MyItemList
{
get { return _MyItemList; }
}
}
I want to draw listbox items on a Canvas, place them somewhere, and then move them around, finding inspiration in the following very good example Brewer Floating Content. However I don't want to duplicate this specific example from the blog.
Hence I attempt to draw items on listbox with a Canvas, but fail again, though in WPF this works easily.
The XAML code is:
<UserControl.Resources>
<DataTemplate x:Key="ThingTemplate" x:DataType="local:Thing">
<Border Width="150" BorderBrush="#FF9B3333" BorderThickness="2"
ManipulationMode="TranslateX, TranslateY, TranslateInertia"
ManipulationDelta="Border_ManipulationDelta">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Border Background="#FF9B3333" Grid.Row="1">
<TextBlock Text="{Binding Comment}" HorizontalAlignment="Center"
VerticalAlignment="Center" TextWrapping="WrapWholeWords" Foreground="Black" Padding="4" />
</Border>
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox x:Name="ThingCanvas" Grid.Row="1" ItemTemplate="{StaticResource ThingTemplate}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
and the source code-behind:
public sealed partial class UserControl1 : UserControl
{
public UserControl1()
{
this.InitializeComponent();
for (var i = 0; i < 4; i++)
{
var newThing = new Thing() { Comment = "Item" + i.ToString() };
ThingCanvas.Items.Add(newThing);
}
}
private void Border_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var left = Canvas.GetLeft(this) + e.Delta.Translation.X;
var top = Canvas.GetTop(this) + e.Delta.Translation.Y;
Canvas.SetLeft(this, left);
Canvas.SetTop(this, top);
}
}
public class Thing
{
public string ImagePath { get; set; }
public string Comment { get; set; }
}
Strange is that the Canvas cannot access to IsItemsHost property, which is set in WPF.
Also hindering is that style setters cannot be databound in UWP, so that Canvas.Left and Canvas.Top cannot be bound.
Thank you for helping me advance on this topic.
RudiAcitivity
I have a program with several features. Each feature has a ViewModel, a MainView and OptionsView. MainView is displaying what the feature does, while the OptionsView is a View allowing the user to change the settings of the feature. OptionsView is stored in the MainView.
I want to centralise the Options into a MainOptions view under a ListView. I can get a List or ObservableCollection of the MainViews for each feature, however i have trouble getting the OptionsView of each feature.
I can display OptionsView in the MainView just fine, however when i try to use DataTemplate in a ListView in order to bind the list of MainViews to it and get the OptionViews it doesn't display anything, but it doesn't crash or output errors.
XAML of OptionsMain :
<Grid>
<ListBox Name="OptionsList" ItemsSource="{Binding Path=FeaturesPages, Mode=TwoWay}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Frame Name="GroupItemFrame" Content="{Binding Path=OptionsPage, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Code behind :
public partial class OptionsMain : Page, INotifyPropertyChanged
{
private ObservableCollection<Page> _optionsPages = new ObservableCollection<Page>();
public ObservableCollection<Page> OptionsPages
{
get { return _optionsPages; }
set
{
_optionsPages = value;
NotifyPropertyChanged("OptionsPages");
}
}
public OptionsMain(ObservableCollection<Page> pages)
{
foreach (Page p in pages)
{
OptionsPages.Add(p);
}
Console.WriteLine("List size : {0}", OptionsPages.Count);
InitializeComponent();
DataContext = this;
Any insight on what might be the problem? Is there a better way of doing this?
This is kind of a tricky issue, and I don't see many ways to accomplish this, but I do have a way. The problem is that you need to call Frame.Navigate to the page for each and every frame. You can't just assign the content unless it's actually a control with content. So I am going to suggest a work around/hack of forcing the frame to navigate as it's being loaded.
Mainpage.xaml:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListBox Name="OptionsList" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Frame Name="GroupItemFrame" Loaded="GroupItemFrame_Loaded" Width="100" Height="100" BorderBrush="Red" BorderThickness="2" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Mainpage.xaml.cs:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<Page> MyPages = new List<Page>();
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
MyPages.Add(new DisplayNumberPage());
OptionsList.ItemsSource = MyPages;
}
int Index = 1;
private void GroupItemFrame_Loaded(object sender, RoutedEventArgs e)
{
Frame MyFrame = sender as Frame;
MyFrame.Navigate(typeof(DisplayNumberPage), Index);
Index++;
}
}
DisplayNumberPage.xaml:
<Grid Background="Black">
<TextBlock x:Name="DisplayNumber" FontSize="30" Text="100" Foreground="White"/>
</Grid>
DispayNumberPage.xaml.cs:
public sealed partial class DisplayNumberPage : Page
{
public DisplayNumberPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DisplayNumber.Text = e.Parameter.ToString();
}
}
I'm adding this as an answer so it can be visible.
I found the optimal answer to my question after i used the answer marked as correct. They both work, it's just another way, simpler i'd argue, to achieve the same result:
<ListBox Name="OptionsList" ItemsSource="{Binding Path=OptionsPages, Mode=TwoWay}" Background="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Name="OptionsItemStack" Orientation="Vertical">
<Label Name="GroupItemLabel" Content="{Binding Path=Title, Mode=OneWay}" Foreground="White"/>
<Frame Name="GroupItemFrame" Content="{Binding}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Notice the Content="{Binding}". It doesn't need a code behind to display the Page in the frame.
As stated, it was tested and works, no errors in the output either.
I am working on a local project and I have some issues.
I want to create a template for some results that have 3 strings(where 1 is a hyperlink) and a picture and they come as an ObservableCollection of results type binded to ItemSource.
public TestClass {
public string Title { get; set; }
public string Description { get; set; }
public string Link { get; set; }
public BitmapImage Thumbnail { get; set; }
}
So, I want to show those results in WPF and I want to use for each item a template and show them in a StackPanel (or ListView).
I tried with ListView but the only thing you can do is select the whole item, but I want also the link to be clickable.
My problem is: how can I create a template to use on each item and then add them in a list that 1 string is clickable?
As Unflux mentioned, that's a good way to do it. And as for the clickable link, use the Hyperlink control like I did below.
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding FirstName}" Grid.Row="0" Grid.Column="0" />
<TextBlock Text="{Binding LastName}" Grid.Row="0" Grid.Column="1" />
<TextBlock Text="{Binding Age}" Grid.Row="0" Grid.Column="2" />
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
<Hyperlink NavigateUri="{Binding BlogAddress}" Click="Hyperlink_OnClick">
<TextBlock Text="{Binding BlogAddress}" />
</Hyperlink>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and code-behind
private void Hyperlink_OnClick(object sender, RoutedEventArgs e)
{
var link = sender as Hyperlink;
Process.Start(link.NavigateUri.ToString());
}
results in
You will probably want to style it a bit and maybe apply a different ItemsPanel to really customize the look of your collection. You can also decorate ItemsControl with scrolling.
Thanks with the idea of using ItemsControl with its template. But the Hyperlink I made it work with Click property and giving to it a:
public ICommand RunHyperlink {
get {
return new ActionCommand(this.ButtonClick);
}
}
private void ButtonClick() {
Process.Start(new ProcessStartInfo(this.Link));
}