My MainWindow.xaml get it´s datacontext from MyClass.cs and it´s view from usercontrol in MyView.xaml as below
<Window.DataContext>
<ViewModel:MyClass/>
</Window.DataContext>
<View:MyView/>
The usercontrol has grid, stackpanel and a button as below
<Grid x:Name="MainGrid" Height="300" Width="502">
<StackPanel x:Name="MainStackPanel" Height="200" Width="400"/>
<Button x:Name="NewButton" Command="{Binding CommandAddNewButton}" Content="new item" Height="25" Width="55" Margin="224,177,223,98"/>
</Grid>
From MyClass I try to add button and textbox to the stackpanel programatically by pressing the "new item" button as below
public ICommand CommandAddNewButton
{ get { return new MyCommand(AddNewButton); } }
private void AddNewButton()
{
var newButton = new Button
{
Name = "Button" + _itemNbr,
Content = "-",
FontSize = 10,
Height = 20,
Width = 15,
Background = new SolidColorBrush(Colors.Beige),
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Margin = new Thickness(10, 10, 10, 10)
};
var newTextBox = new TextBox
{
Name = "TextBox" + _itemNbr,
Height = 20,
Width = 70,
FontSize = 10,
HorizontalContentAlignment = HorizontalAlignment.Right,
VerticalContentAlignment = VerticalAlignment.Center
};
MainStackPanel.Children.Add(newButton);
MainStackPanel.Children.Add(newTextBox);
}
A new button and textboxe are created after button press but they do not displayed. How can I fix it? All help are appreciated.
<UserControl x:Class="WpfApp1.View.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup- compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModel="clr-namespace:WpfApp1.ViewModel"
xmlns:View="clr-namespace:WpfApp1.View">
<Grid x:Name="MainGrid" Height="300" Width="502">
<StackPanel x:Name="MainStackPanel" Height="200" Width="400"/>
<Button x:Name="NewButton" Command="{Binding CommandAddNewButton}" Content="new item" Height="25" Width="55" Margin="224,177,223,98"/>
</Grid>
</UserControl>
I think you are inheriting your View(Myview) to your viewmodel(Myclass). so that you are accessing a different instance of stackpanel. One method you can do is pass stackpanel as a paramater to the view model .
In Xaml:
<Button x:Name="NewButton" Command="{Binding CommandAddNewButton}" CommandParameter="{Binding ElementName=MainStackPanel}" Content="new item" Height="25" Width="55" Margin="224,177,223,98"/>
In ViewModel:
private void AddNewButton(object obj)
{
var MainStackPanel= obj as StackPanel;
}
Related
I have an application that receives from the user a list of string.
The application needs to display that list and provide a TextBox per each string (for additional user input).
Eventually the application will gather the values from the TextBox elements, one value per user input-string.
The layout of the application is a Grid based layout. Here is a mockup example developed in Python tkinter:
I am trying to build the same thing in WPF and can't find the way to correctly bind the items.
I tried multiple approaches, none results in a working window.
Here is my latest version:
XAML
<Grid x:Name="MainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20*"/>
<RowDefinition Height="10"/>
<RowDefinition Height="20"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Opcode" Grid.Column="0" Grid.Row="0" VerticalAlignment="Bottom" FontFamily="Courier New" HorizontalAlignment="Center"/>
<Label Content="Operand" Grid.Column="1" Grid.Row="0" VerticalAlignment="Bottom" FontFamily="Courier New" HorizontalAlignment="Center"/>
<ComboBox x:Name="Opcodes" Grid.Column="0" Grid.Row="1" Height="22" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center" IsTextSearchEnabled="False" SelectionChanged="Opcodes_SelectionChanged"/>
<TextBox x:Name="Operand" Grid.Column="1" Grid.Row="1" Height="20" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center" IsUndoEnabled="False" IsEnabled="False"/>
<Label Content=">" Grid.Column="2" Grid.Row="1" FontFamily="Courier New" FontSize="11" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<ItemsControl x:Name="PinGrid" Grid.Row="0" Grid.Column="3" Margin="0,0,0,4" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" Margin="2" Width="14" FontSize="14" FontWeight="Medium" FontFamily="Courier New" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="PinDataGrid" Grid.Row="1" Grid.Column="3" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<WrapPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Center">
<Button x:Name="Cancel" Content="_Cancel" IsCancel="True" Width="50" Click="Cancel_Click"/>
<Button x:Name="Ok" Content="_Ok" Margin="10,0,0,0" IsDefault="True" Width="50" Click="Ok_Click"/>
</WrapPanel>
</Grid>
Code Behind
private ObservableCollection<string> Pins { get; set; } = null;
private List<string> PinsData { get; set; } = null;
private string OperandData { get; set; } = null;
(window c-tor)
Pins = new ObservableCollection<string>(<user provided list of pins>);
PinGrid.ItemsSource = Pins;
PinsData = new List<string>();
for(int i = 0; i < Pins.Count; i++)
{
PinsData.Add("X");
TextBox t = new TextBox()
{
Margin = new Thickness(2, 0, 2, 0),
Padding = new Thickness(0),
Width = 14,
FontSize = 13,
FontWeight = FontWeights.Medium,
FontFamily = new FontFamily("Courier New"),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center
};
Binding b = new Binding();
b.Mode = BindingMode.TwoWay;
b.Path = new PropertyPath("PinsData[" + i + "]");
t.SetBinding(TextBox.TextProperty, b);
PinDataGrid.Items.Add(t);
}
Opcodes.Items.Add(String.Empty); // add empty entry for clear operation
foreach (string opcode in <user provided list of opcodes>)
Opcodes.Items.Add(opcode);
DataContext = this;
Here is the WPF version:
As you can see, the X is not showing in the TextBox and the binding didn't happen. What am I doing wrong? How can I create the two way binding of text boxes to the PinsData list of strings?
It might be that my whole approach is wrong - so such kinds of inputs will be more than welcomed.
I'll try to tell you why it didn't work for you first.
In C# strings are immutable so, as you were binding using a two-way mode, every time you changed the string it was a different object instance. Try changing your b.Mode = BindingMode.TwoWayto OneWay and you will see the "X" there, but it won't work for what you want.
Managed to get the link I used once when I faced a similar issue
binding string
To achieve what you want you should do the following:
Create a new class to control your item changes like this:
public class PinsDataItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
string text;
public PinsDataItem(string value)
{
this.Text = value;
}
public string Text
{
get
{
return text;
}
set
{
if (text != value)
{
text = value;
}
NotifyPropertyChanged("Value");
}
}
}
Your forshould change to this:
PinsData = new List<PinsDataItem>();
for (int i = 0; i < Pins.Count; i++)
{
PinsData.Add(new PinsDataItem("X"));
TextBox t = new TextBox()
{
Margin = new Thickness(2, 0, 2, 0),
Padding = new Thickness(0),
Width = 14,
FontSize = 13,
FontWeight = FontWeights.Medium,
FontFamily = new FontFamily("Courier New"),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center
};
Binding b = new Binding();
b.Mode = BindingMode.TwoWay;
b.Source = PinsData[i];
b.Path = new PropertyPath("Text");
t.SetBinding(TextBox.TextProperty, b);
PinDataGrid.Items.Add(t);
}
This should work for you. Let me know if it didn't :D
I am adding dynamic elements to the screen. When the elements fill up the container I am putting them in, I want a vertical scroll bar to appear so that the user can scroll through all of the elements added. The code is performing as expected.
The problem is that when the elements are added to the interface, they are added in a haphazard and weird way. They start in the middle of the grid element, and then shift up. I want them to be added to the top left of the control, then to the right, all the way down the page with equal spacing between the rows. Then when there are too many on the screen, have the scroll bar appear.
Here is all of the code that is running this test. Thanks in advance for any help!
xaml:
<Window x:Class="GPWorkouts.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:GPWorkouts"
mc:Ignorable="d"
Title="MainWindow" Height="370" Width="1000" >
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Width="500">
<StackPanel Orientation="Horizontal">
<Button Name="buttonAddNewPlayer" Content="Add New Player" Margin="5" Click="buttonAddNewPlayer_Click" />
</StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" Height="300">
<UniformGrid Columns="2" Name="playerContainer" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.CanContentScroll="True" >
</UniformGrid>
</ScrollViewer>
</StackPanel>
</StackPanel>
</Grid>
</Window>
c# code behind:
using log4net;
using System.Windows;
using System.Windows.Controls;
namespace GPWorkouts
{
public partial class MainWindow : Window
{
private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
int x = 1;
public MainWindow()
{
InitializeComponent();
}
private void buttonAddNewPlayer_Click(object sender, RoutedEventArgs e)
{
Button buttonDyno = new Button()
{
Content = x,
Width = 150,
Height = 32,
Margin = new Thickness(5),
};
x++;
playerContainer.Children.Add(buttonDyno);
}
}
}
the observed effect can be explaned by UniformGrid arrange approach. Buttons start in the middle of the grid element, and then shift up, because UniformGrid has less and less space after each button added.
the effect can be smoothed if you add VerticalAlignment = VerticalAlignment.Top in Button properties.
but I would suggest to use a different panel : WrapPanel
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Width="500">
<StackPanel Orientation="Horizontal">
<Button Name="buttonAddNewPlayer"
Margin="5"
Click="buttonAddNewPlayer_Click"
Content="Add New Player" />
</StackPanel>
<ScrollViewer Height="300"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<WrapPanel Name="playerContainer"
Background="Wheat"
Orientation="Horizontal" />
</ScrollViewer>
</StackPanel>
</StackPanel>
</Grid>
private void buttonAddNewPlayer_Click(object sender, RoutedEventArgs e)
{
Button buttonDyno = new Button
{
Content = x,
Width = 150,
Height = 32,
Margin = new Thickness(5),
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Center
};
x++;
playerContainer.Children.Add(buttonDyno);
// ensure 2 controls per row
playerContainer.ItemWidth = playerContainer.ActualWidth/2;
}
So I have this scenario in which I am showing a Grid inside a ScrollViewer.
I want to show a combobox and an image along the scrollbar in a way that it doesn't effect the scrolling functionality,
Something like this:
Currently whenever the scrollviewer becomes visible it appears in a new row, how can I show it along the controls in the same row?
Here is my xaml design:
<DockPanel LastChildFill="True">
<!--Top Panel-->
<Grid DockPanel.Dock="Top">
--GridContent
</Grid>
<!--Bottom Panel-->
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column ="0">
</ComboBox>
<Image Grid.Column="1">
</Image>
</Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Stretch"
VerticalScrollBarVisibility="Auto" VerticalAlignment="Stretch" >
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
-- Grid Content
</Grid>
</ScrollViewer>
</DockPanel>
Currently it appears like this :
I haven't looked at doing this in XAML, but you can do it like this in the code behind:
public partial class MainWindow : Window
{
private void IncrementColumn(UIElement element)
{
Grid.SetColumn(element, Grid.GetColumn(element) + 1);
}
public MainWindow()
{
InitializeComponent();
scrollPanel.ApplyTemplate();
var horizontal = scrollPanel.Template.FindName("PART_HorizontalScrollBar", scrollPanel) as ScrollBar;
var vertical = scrollPanel.Template.FindName("PART_VerticalScrollBar", scrollPanel) as ScrollBar;
var presenter = scrollPanel.Template.FindName("PART_ScrollContentPresenter", scrollPanel) as ScrollContentPresenter;
var corner = scrollPanel.Template.FindName("Corner", scrollPanel) as Rectangle;
var grid = corner.Parent as Grid;
grid.ColumnDefinitions.Insert(0, new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
IncrementColumn(horizontal);
IncrementColumn(vertical);
IncrementColumn(corner);
Grid.SetColumnSpan(presenter, 2);
var panel = new StackPanel() { Orientation = Orientation.Horizontal };
panel.Children.Add(new ComboBox());
panel.Children.Add(new Image());
Grid.SetRow(panel, 1);
Grid.SetColumn(panel, 0);
grid.Children.Add(panel);
}
}
Here's the XAML to go with it:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow" Height="150" Width="525">
<ScrollViewer
Name="scrollPanel"
HorizontalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch"
VerticalScrollBarVisibility="Auto"
VerticalAlignment="Stretch">
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"/>
</Grid>
</ScrollViewer>
</Window>
I'm learning WPF and I have some troubles with it. I made this XAML:
<Window.Resources>
<DataTemplate x:Key="TemplateTest">
<Button Margin="10"
BorderThickness="2"
Content="{Binding Path=Text}">
<Button.Effect>
<DropShadowEffect BlurRadius="20" />
</Button.Effect>
</Button>
</DataTemplate>
</Window.Resources>
<StackPanel x:Name="StackPanel">
<TextBox x:Name="TextBox"
Margin="10">TextBox</TextBox>
<ContentControl Content="{Binding ElementName=TextBox, Path=.}"
ContentTemplate="{StaticResource ResourceKey=TemplateTest}" />
</StackPanel>
and the code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var Resource = this.Resources["TemplateTest"] as DataTemplate;
StackPanel.Children.Add(
new ContentControl()
{
Content = new Binding()
{
ElementName = "TextBox",
Path = new PropertyPath(".")
},
ContentTemplate = Resource,
});
}
}
My problem is that the textbox's text only appear in the XAML defined control.
How to make it work in the code behind too?
You're setting the ContentControl.Content to the Binding, which is not the same as binding the Content property to a value.
To bind a property in the code behind, you need syntax like this:
var newControl new ContentControl();
newControl.ContentTemplate = Resource;
Binding b = new Binding();
b.ElementName = "TextBox";
b.Path = new PropertyPath(".");
myContentControl.SetBinding(ContentControl.ContentProperty, b);
I am developing a UserControl that consists of a block with a heading and a list of items (as ItemsControl). The usercontrol is added dynamically to a canvas. I need to get the actual size of the control (including space taken by ItemsControl) before it gets rendered. I tried overriding MeasureOverride method of the UserControl hoping that the size would be reflected in DesiredSize property. But it is not working.
The XAML is:
<UserControl x:Class="MyTools.MyControl"
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"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="LayoutRoot" Background="White">
<Border Name="MainBorder" CornerRadius="5" BorderThickness="2" BorderBrush="Black">
<Grid Name="grid1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="34" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Name="titleGrid" Grid.Row="0" Background="#FF727272">
<TextBlock Name="titleText" HorizontalAlignment="Center" Text="{Binding ControlName}" VerticalAlignment="Center" FontSize="13" FontWeight="Bold" Foreground="Beige" />
</Grid>
<Grid Name="gridpr" Grid.Row="1" Background="#12C48F35">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Name="borderPr" CornerRadius="3" Margin="10" BorderThickness="1" BorderBrush="LightGray" Grid.Row="0">
<Grid Name="gridPr" Background="#FFC1C1C1" MouseLeftButtonUp="gridPr_MouseLeftButtonUp">
<StackPanel>
<TextBlock HorizontalAlignment="Center" Name="txtPr" Text="SubItems" VerticalAlignment="Center" Foreground="#FF584848" FontSize="12" />
<ItemsControl x:Name="pitems" ItemsSource="{Binding MyItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="15,0,0,0">
<TextBlock Text="{Binding MyVal}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Grid>
</UserControl>
I am overriding MeasureOverride of the UserControl as shown below:
namespace MyTools
{
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
public string ControlName { get; set; }
public object MyItems { get; set; }
public class Row
{
public string MyVal { get; set; }
}
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = base.MeasureOverride(availableSize);
var sideLength = Math.Min(desiredSize.Width, desiredSize.Height);
desiredSize.Width = sideLength;
desiredSize.Height = sideLength;
return desiredSize;
}
}
}
Client Code:
MyControl control1 = new MyControl();
control1.ControlName = "Test Name";
var test = new List<MyControl.Row>(
new MyControl.Row[]
{
new MyControl.Row {MyVal = "Item1"},
new MyControl.Row {MyVal = "Item2"},
new MyControl.Row {MyVal = "Item3"}
});
control1.MyItems = test;
control1.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
MessageBox.Show(control1.DesiredSize.Height.ToString());
canvas1.Children.Add(control1);
I am not getting the actual size using the DesiredSize.Height property from the client. Any idea on how to fix this?
Is your user control defined to change in height to reflect the size of the contents? By default your user control will be a fixed size and not change in height just because the items control has more entries.
I think you need to add your user control inside a grid before then measuring the grid. This is how I measure controls and it seems to work well, even when measuring the control directly does not work as in your case...
MyControl control1 = new MyControl();
... your setup code for control1...
Dim containerGrid As New Grid
containerGrid.Children.Add(control1)
containerGrid.Measure(New Size(Double.MaxValue, Double.MaxValue))
containerGrid.Arrange(New Rect(0, 0, Double.MaxValue, Double.MaxValue))
...now check the grid.ActualWidth and grid.ActualHeight
Try using ActualWidth and ActualHeight properties.
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = new Size();
var sideLength = Math.Min(ActualWidth, ActualHeight);
desiredSize.Width = sideLength;
desiredSize.Height = sideLength;
return desiredSize;
}