Adding multiple stack panels through C# code - c#

I have a mom with multiple children. Each child has a name already assigned and needs user input for the value.
I am trying to generate a StackPanel with a TextBlock and TextBox for each child through code. The number of children are unknown so I need a loop to create each StackPanel and them to a "mom" StackPanel which is added to a Border control. When I run the code it runs through the loop fine but it does not show anything in the display.
Here is the code:
var momStackPanel = new StackPanel() { Orientation = Windows.UI.Xaml.Controls.Orientation.Vertical};
foreach (ChildItem item in ChildList)
{
var childItemSP = new StackPanel();childItemSP.Orientation = Orientation.Horizontal;
TextBlock nameTB = new TextBlock();
name.Text = item.Name;
childItemSP.Children.Add(nameTB);
TextBox valueTB = new TextBox();
valueTB .Text = item.Value;
childItemSP.Children.Add(valueTB);
}
BorderSection.Child = momStackPanel;
BorderSection.Visibility = Windows.UI.Xaml.Visibility.Visible;
Here is the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="t4" Text="t4" Grid.Row="0"/>
<Border Grid.Row="1" HorizontalAlignment="Stretch">
<StackPanel >
<TextBlock x:Name="r1" Text="{Binding r1}" HorizontalAlignment="Center" />
<TextBlock x:Name="r2" Text="r2" Foreground="DarkGray"/>
</StackPanel>
</Border>
<Border x:Name="BorderSection" Grid.Row="2">
</Border>
<StackPanel x:Name="CommentPanel" Orientation="Vertical" Grid.Row="3" Margin="6,6,6,6">
<TextBox x:Name="Comment" Text="" MinHeight="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Button x:Name="Submit" Content="Submit" Click="Submit_Click_1"/>
<Button x:Name="Cancel" Content="Cancel" Click="Cancel_Click_1"/>
</StackPanel>
</Grid>
Any ideas how to deal with this?

The ItemsControl control acts like a StackPanel with a variable number of items. You can bind it to an ObservableCollection, so you don't have to mess with the view at all in the code-behind.
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.Resources>
<ResourceDictionary Source="ChildTemplate.xaml"/>
</ItemsControl.Resources>
</ItemsControl>
Then you can provide the child's view in a separate file (ChildTemplate.xaml).
<DataTemplate DataType="{x:Type myNamespace:ChildViewModel}">
<StackPanel >
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
Don't forget you will have to call OnPropertyChanged for the ObservableCollection whenever you change it.

Related

Create multiple DataGrids in WPF dynamically

I am trying to dynamically create DataGrids in WPF, one below the other in a Grid. the problem is that I don't even know where to start, I saw that it could be done through code but I would like to use XAML correctly.
void PopulateDatagridSQL(string Data, string index, string TabName)
{
try
{
Data = Data.Replace((Char)6, (Char)124);
List<DataTable> Results = new List<DataTable>();
Results = JsonConvert.DeserializeObject<List<DataTable>>(Data);
foreach (SQLWindow SingleSQLWindows in StaticVar.MySQLWindows)
{
if (SingleSQLWindows.MyINDEX == index)
{
SingleSQLWindows.Dispatcher.Invoke(new Action(() =>
{
foreach (TabItem item in SingleSQLWindows._tabItems)
{
if (item.Name == TabName)
{
//create multiple datagrids up to results.count
((SQLPage)((Frame)item.Content).Content).DataGrid1.ItemsSource = Results[0].DefaultView;//foreach
((SQLPage)((Frame)item.Content).Content).TxtSqlLog.Text = "Records in Datagrid: " + Results[0].Rows.Count;//foreach
}
}
}));
}
}
}
catch (Exception asd)
{
foreach (SQLWindow SingleSQLWindows in StaticVar.MySQLWindows)
{
if (SingleSQLWindows.MyINDEX == index)
{
SingleSQLWindows.Dispatcher.Invoke(new Action(() =>
{
foreach (TabItem item in SingleSQLWindows._tabItems)
{
if (item.Name == TabName)
{
((SQLPage)((Frame)item.Content).Content).TxtSqlLog.Text = "Error in $SqlResponse";
}
}
}));
}
}
}
}
In this function I receive a list of DataTables and for each DataTable I have to create a DataGrid.
<Page x:Class="Emergency_APP_Server_WPF.Forms.SQLPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Emergency_APP_Server_WPF.Forms"
mc:Ignorable="d" x:Name="SQLPageXaml" Loaded="SQLPage_Loaded"
d:DesignHeight="450" d:DesignWidth="800"
Title="SQLPage" >
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="100"></RowDefinition>
<RowDefinition Height="0"></RowDefinition>
<RowDefinition Height="*" MinHeight="150"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
<avalonEdit:TextEditor Grid.Column="0" Background="White"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="MyAvalonEdit" FontFamily="Consolas"
FontSize="11pt" Margin="10,10,10,10" ShowLineNumbers="True"
LineNumbersForeground="Gray" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" KeyUp="MyAvalonEdit_KeyUp"/>
<Button Grid.Column="1" x:Name="BtnSendSQL" Margin="0,10,10,10" Click="BtnSendSQL_Click">
<StackPanel>
<Image Height="25" Source="..//Resources/send.png"></Image>
<TextBlock Margin="0,10,0,0" VerticalAlignment="Top" Foreground="White" Text="SendSQL"></TextBlock>
</StackPanel>
</Button>
</Grid>
<GridSplitter Margin="0,-10,0,0" Grid.Row="1" HorizontalAlignment="Stretch" Background="Transparent" ResizeBehavior="PreviousAndNext">
</GridSplitter>
<DataGrid Grid.Row="2" x:Name="DataGrid1" RowStyle="{StaticResource DataGridRowSql}" Style="{StaticResource DataGridStyleSQL}" >
<DataGrid.CommandBindings>
<CommandBinding Command="Copy" Executed="CommandBinding_Executed"></CommandBinding>
</DataGrid.CommandBindings>
<DataGrid.InputBindings>
<KeyBinding Key="C" Modifiers="Ctrl" Command="Copy"></KeyBinding>
</DataGrid.InputBindings>
</DataGrid>
<TextBlock TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Bottom" Grid.Row="3" x:Name="TxtSqlLog" Text="Wait For Commands..."
FontSize="14" FontFamily="Consolas" Foreground="White"></TextBlock>
</Grid>
</Grid>
I do not know the exact layout where you want to put all the DataGrids, so I just assume you want to place them in row 2 of your Grid. As you want to stack them, you can use an ItemsControl in a ScrollViewer. The latter is just there, so you can scroll through the DataGrids in case the page is too small to display them all. So this is the basic layout for the solutions below.
<Grid>
<Grid.RowDefinitions>
<!-- ...your row definitions. -->
</Grid.RowDefinitions>
<!-- ...your other controls. -->
<ScrollViewer Grid.Row="2">
<ItemsControl/>
</ScrollViewer>
</Grid>
Code-behind solution
In code-behind, you need to access the ItemsControl, so assign it a name.
<ItemsControl x:Name="DataGridContainer"/>
Then just create DataGrids in a loop from your DataTable list.
foreach (var dataTable in Results)
{
var dataGrid = new DataGrid { ItemsSource = dataTable.DefaultView };
DataGridContainer.Items.Add(dataGrid);
};
By the way, in this scenario, you could also use a StackPanel instead of an ItemsControl.
MVVM solution
You can create a property in your view model that exposes the default views of your DataTables.
public ObservableCollection<DataView> DataViews { get; }
Make sure you instantiate the collection in the constructor or implement INotifyPropertyChanged, so that the collection is available in XAML. Then bind the ItemsControl to this collection.
<ItemsControl ItemsSource="{Binding DataViews}"/>
Next create a DataTemplate for the DataView type. As each data view in the bound collection represents a data table that should be displayed as a DataGrid, it would look like this.
<DataTemplate DataType="{x:Type data:DataView}">
<DataGrid ItemsSource="{Binding}"/>
</DataTemplate>
Then you have to assign this data template as ItemTemplate in the ItemsControl. I just inline it here, but you can also put it in any ResourceDictionary and reference it via StaticResource.
<ItemsControl ItemsSource="{Binding DataViews}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type data:DataView}">
<DataGrid ItemsSource="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now, you just have to add the default views to the DataViews collection. As this collection is an ObservableCollection, it will notify the ItemsControl to update on each add.
foreach (var dataTable in Results)
{
DataViews.Add(dataTable.DefaultView);
};
As an item is added, the ItemsControl will get notified and creates an item using the DataTemplate.
I recommend you to use the MVVM solution, as it separates the presentation from your data and is much easier to realize and customization of the DataGrids via the DataTemplate is much easier, convenient and maintainable in XAML.
<Page x:Class="Emergency_APP_Server_WPF.Forms.SQLPage"
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:z="clr-namespace:System.Data;assembly=System.Data"
xmlns:local="clr-namespace:Emergency_APP_Server_WPF.Forms"
mc:Ignorable="d" x:Name="SQLPageXaml" Loaded="SQLPage_Loaded"
d:DesignHeight="450" d:DesignWidth="800"
Title="SQLPage" >
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="100"></RowDefinition>
<RowDefinition Height="0"></RowDefinition>
<RowDefinition Height="*" MinHeight="150"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="70"></ColumnDefinition>
</Grid.ColumnDefinitions>
<avalonEdit:TextEditor Grid.Column="0" Background="White"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="MyAvalonEdit" FontFamily="Consolas"
FontSize="11pt" Margin="10,10,10,10" ShowLineNumbers="True"
LineNumbersForeground="Gray" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" KeyUp="MyAvalonEdit_KeyUp"/>
<Button Grid.Column="1" x:Name="BtnSendSQL" Margin="0,10,10,10" Click="BtnSendSQL_Click">
<StackPanel>
<Image Height="25" Source="..//Resources/send.png"></Image>
<TextBlock Margin="0,10,0,0" VerticalAlignment="Top" Foreground="White" Text="SendSQL"></TextBlock>
</StackPanel>
</Button>
</Grid>
<GridSplitter Margin="0,-10,0,0" Grid.Row="1" HorizontalAlignment="Stretch" Background="Transparent" ResizeBehavior="PreviousAndNext">
</GridSplitter>
<ScrollViewer Grid.Row="2">
<ItemsControl ItemsSource="{Binding DataViews}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type z:DataView}">
<DataGrid RowStyle="{StaticResource DataGridRowSql}" Style="{StaticResource DataGridStyleSQL}" ItemsSource="{Binding}">
<DataGrid.CommandBindings>
<CommandBinding Command="Copy" Executed="CommandBinding_Executed"></CommandBinding>
</DataGrid.CommandBindings>
<DataGrid.InputBindings>
<KeyBinding Key="C" Modifiers="Ctrl" Command="Copy"></KeyBinding>
</DataGrid.InputBindings>
</DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<TextBlock TextAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Bottom" Grid.Row="3" x:Name="TxtSqlLog" Text="Wait For Commands..."
FontSize="14" FontFamily="Consolas" Foreground="White"></TextBlock>
</Grid>
</Grid>
public ObservableCollection<DataView> _DataViews = new ObservableCollection<DataView>();
public ObservableCollection<DataView> DataViews
{
get
{
return _DataViews;
}
set
{
_DataViews = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string Param = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Param));
}
foreach (SQLWindow SingleSQLWindows in StaticVar.MySQLWindows)
{
if (SingleSQLWindows.MyINDEX == index)
{
SingleSQLWindows.Dispatcher.Invoke(new Action(() =>
{
foreach (TabItem item in SingleSQLWindows._tabItems)
{
if (item.Name == TabName)
{
foreach (DataTable itemTable in Results)
{
//SingleSQLWindows.DataViews.Add(itemTable.DefaultView);
////create more datagrid while results.count
///
((SQLPage)((Frame)item.Content).Content).DataViews.Add(itemTable.DefaultView);
//((SQLPage)((Frame)item.Content).Content).TxtSqlLog.Text = "Records in Datagrid: " + Results[0].Rows.Count;//foreach
}
}
}
}));
}
}
the visual studio does not report any errors but the scrollviewer remains empty, I cannot understand why

How to assign grid row and column index dynamically

I am trying to add user controls to the Grid and I am trying to set the Grid.Column and Grid.Row property in the DataTemplate but it does not have any effect on the rendering. Can someone help to point out what could be wrong in the code below.
I have the main window with the following code:
<ItemsControl ItemsSource="{Binding Controls}">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<AppControls:TagInfo Grid.Row="{Binding RowIndex}"
Grid.Column="{Binding ColumnIndex}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
TagInfo.xaml.cs
public partial class TagInfo : UserControl
{
public TagInfo()
{
InitializeComponent();
}
public int RowIndex { get; set; }
public int ColumnIndex { get; set; }
}
TagInfo.xaml
<UserControl x:Class="DataSimulator.WPF.Controls.TagInfo"
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"
mc:Ignorable="d" Height="258" Width="302"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid Margin="2,2,2,2">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Tag"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding TagInfo.TagName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="High High"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TagInfo.HighHigh}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="High"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TagInfo.High}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Low Low"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding TagInfo.LowLow}"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Low"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding TagInfo.Low}"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Range Start"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding TagInfo.RangeStart}"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Range End"/>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding TagInfo.RangeEnd}"/>
<Button Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2" Content="Save"/>
</Grid>
</UserControl>
ViewModel
private ObservableCollection<Controls.TagInfo> _controls
= new ObservableCollection<Controls.TagInfo>();
public ObservableCollection<Controls.TagInfo> Controls
{
get { return _controls; }
set { _controls = value; }
}
private void AddControl()
{
if(currentRow == 3)
{
currentRow = 0;
}
if(currentColumn == 3)
{
currentColumn = 0;
currentRow++;
}
var tagInfoUserControl = new Controls.TagInfo();
tagInfoUserControl.RowIndex = currentRow;
tagInfoUserControl.ColumnIndex = currentColumn++;
_controls.Add(tagInfoUserControl);
}
I am trying to add user controls to the grid and I am trying to set the Grid.Column and Grid.Row property in the data template but it does not have any effect on the rendering
We can't do the attached property binding in a DataTemplate, creating a style for your UserControl in ItemsControl.ItemContainerStyle can make it work:
<ItemsControl ItemsSource="{Binding Controls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="AppControls:TagInfo">
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Screenshot:
when all columns and rows have to be of equal size and elements are consequtive, it is probably simpler to use UniformGrid. Rows and Columns properties support binding.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Name="MainGrid" Rows="4" Columns="4"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
here is a nice example of Grid ItemsPanel here: WPF Grid as ItemsPanel for a list dynamically bound to an ItemsControl

Bind ItemsSource to Column of ComboBoxes

As per title. The column is in a DataTemplate.
This is what I have currently:
var test = FindChildControl<ComboBox>(this, "PrintCode") as ComboBox;
test.ItemsSource = listPrintCode;
MessageBox.Show(test.Items.Count.ToString());
FindChildControl method:
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
var fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
return null;
}
XAML - Template
<DataTemplate x:Key="lbCommsItemSetTemplate">
<Grid Margin="0" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBlock Grid.Row="0" Grid.Column="0" Foreground="Blue" Margin="40,0,0,0"
Text="{Binding CommonDesc}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Serial:" Margin="0,0,0,0"/>
<TextBlock Grid.Row="1" Grid.Column="0" Foreground="Blue" Margin="35,0,0,0"
Text="{Binding Serial}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="lbIssueTemplate">
<Grid Margin="0" Width="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBlock Grid.Row="0" Grid.Column="0" Foreground="Blue" Margin="50,0,0,0"
Text="{Binding CommonDesc}" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Qty:" Margin="10,0,0,0"/>
<TextBox Grid.Row="0" Grid.Column="1" Foreground="Blue" Margin="35,0,0,0"
Text="{Binding LoanQty}" PreviewTextInput="UIElement_OnPreviewTextInput" MaxLength="4"
GotKeyboardFocus="UIElement_OnGotKeyboardFocus" MaxLines="1"/>
<ComboBox x:Name="PrintCode" Grid.Row="0" Grid.Column="2" ItemsSource="{Binding}"
SelectedValuePath="PrintCode" DisplayMemberPath="PrintCode"/>
<CheckBox Grid.Row="0" Grid.Column="3" IsChecked="{Binding PrintShortSerial}"/>
</Grid>
</DataTemplate>
XAML - ListBox that implements the Template
<telerik:RadListBox Grid.Row="0" Grid.Column="2" Margin="0, 5, 5, 5"
x:Name="listBoxIssue" HorizontalAlignment="Left" VerticalAlignment="Top"
Height="690" Width="793"
ItemTemplate="{StaticResource lbIssueTemplate}" ItemsSource="{Binding}"
SelectionMode="Multiple" Drop="ListBoxIssue_OnDrop"/>
The message box is just simply to confirm that 'listPrintCode' and 'FindChildControl' is working as intended. But the ComboBox didn't display anything, even if it's just 1 ComboBox. If I apply the codes to a normal ComboBox not part of the template, it's all fine. I think there's an obvious flaw in my code, which is that there's nothing that seems to apply to all ComboBoxes in the column. So my question is, how do I bind my ItemsSource as the column of the comboboxes?
Note: The number of rows (ComboBoxes) are not fixed.

How to wrap TextBlock content in TreeView?

I have TreeView, which displays some data using data templates. Here's XAML:
<TreeView Grid.Row="0" ItemsSource="{Binding Railways}" x:Name="tvDatawareObjects"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<!-- other templates here... -->
<HierarchicalDataTemplate DataType="{x:Type viewModels:ProjectViewModel}" ItemsSource="{Binding Phases}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
<TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:CollectionViewModel}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding CollectionName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Text wrapping for <TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" /> doesn't work. What am I doing wrong?
I believe the TextBlock isn't wrapping because it doesn't have a defined width. The grid column that the TextBlock is in has a * width which will grow as the TextBlock grows in width. Try setting a width on the TextBlock or the column and see if the change causes the TextBlock to wrap.
Update:
To be more specific, the problem is that the TreeViewItem will size itself to the size of its contents, the ColumnDefinition will fill the (infinitely) available space and the TextBlock, with no width restriction, will never wrap. This post does a good job of describing how the TreeViewItem behaves. To sum it up: the content area of the TreeViewItem is set to 'Auto' so it will grow to fit the contents. To explicitly set the width of the TreeViewItem try binding your ColumnDefinition width to the TreeView's ActualWidth.
XAML:
<TreeView Width="100">
<TreeViewItem>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="Lorem Ipsum" />
<TextBlock Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."
TextWrapping="Wrap" Grid.Row="1"/>
</Grid>
</TreeViewItem>
</TreeView>
I did it. :)
Accordind to the link, provided in the Dan's answer, the reason is lying in the default TreeViewItemTemplate.
Unfortunately, simple binding to TreeView.ActualWidth can't help. This is because width of each item is less than TreeView.ActualWidth by definition - items are rendered with some horizontal offset, depending on their level.
Hence, to solve the problem, I need to calculate width of item like this:
width = actual_width_of_tree_view - relative_horizontal_offset_of_item
To be more precise, I need ScrollViewer.ViewportWidth, so as TreeViewContent might be scrolled vertically, and visible area of TreeView will be less than TreeView.ActualWidth in this case.
Here's attached property:
public static Double GetProjectTitleWidth(DependencyObject obj)
{
return (Double)obj.GetValue(ProjectTitleWidthProperty);
}
public static void SetProjectTitleWidth(DependencyObject obj, Double value)
{
obj.SetValue(ProjectTitleWidthProperty, value);
}
public static readonly DependencyProperty ProjectTitleWidthProperty = DependencyProperty.RegisterAttached(
"ProjectTitleWidth",
typeof(Double),
typeof(DatawareSearchView),
new UIPropertyMetadata(0.0, ProjectTitleWidthChanged));
private static void ProjectTitleWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var targetElement = d as FrameworkElement;
if (targetElement != null)
{
var bindingExpr = targetElement.GetBindingExpression(ProjectTitleWidthProperty);
var sourceElement = bindingExpr.DataItem as FrameworkElement;
if (sourceElement != null)
{
// calculating relative offset
var leftTop = targetElement.TranslatePoint(new Point(0.0, 0.0), sourceElement);
// trying to find ScrollViewer
var border = VisualTreeHelper.GetChild(sourceElement, 0);
if (border != null)
{
var scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer;
if (scrollViewer != null)
{
// setting width of target element
targetElement.Width = scrollViewer.ViewportWidth - leftTop.X;
}
}
}
}
}
...and markup:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
<TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" x:Name="tbTitle" Grid.Row="1"
localviews:DatawareSearchView.ProjectTitleWidth="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
</Grid>
Of course, provided solution isn't universal - it assumes, that TreeView has Border and ScrollViewer.
try this
<TextBlock Text="{Binding Model.Title}" Width="{Binding ActualWidth,
ElementName=tvDatawareObjects}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1"/>

Grid height not adjusting properly when a row height with SharedSizeGroup changes

I have two grids with three rows each. The first and last row of each grid has a fixed height while the middle rows have auto height and share their height using SharedSizeGroup.
First, the content of the right grid determines the height of the size group. When the content of the right grid shrinks so that the content of the left grid determines the height of the size group, the overall size of the left grid is not adjusted correctly.
See the following sample app. Click the increase button to increase the size of the textblock in the right grid. The size of the left grid changes accordingly. Now decrease the size. When the textblock becomes smaller than the textblock in the left grid, the total size of the left grid doesn't shrink. Is this a bug or am i missing something?
<StackPanel Orientation="Horizontal" Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<Grid Background="Green">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" />
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150" />
<TextBlock Background="Blue" Grid.Row="2" />
</Grid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
<Button Height="24" Click="Button_Click_1" VerticalAlignment="Top">Increase</Button>
<Button Height="24" Click="Button_Click_2" VerticalAlignment="Top">Decrease</Button>
</StackPanel>
private void Button_Click_1(object sender, RoutedEventArgs e)
{
textBlock2.Height += 30;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
textBlock2.Height -= 30;
}
The rows are staying the same height - the size of the second TextBlock is changing, while the size of the first TextBlock remains 100.
To demonstrate this, make the following changes:
Add ShowGridLines="True" to each of your Grids
Change your named TextBlocks to show their ActualHeight as their text:
<TextBlock Background="Red" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
You will see that the SharedSizeGroup row will be the maximum ActualHeight of the two TextBlocks.
Update: A Short Project To Show What's Happening
I've created a quick-n-dirty project to show what's happening. It turns out that when the right grid gets smaller than the original size, the left grid does an Arrange but not a Measure. I am not sure I understand this completely - I have posted a follow-up question with this same solution.
Here is the solution that I created, based on your original. It simply wraps the basic grid in a custom class that spews out info (via event) when Measure and Arrange are called. In the main window, I just put that info into a list box.
InfoGrid and InfoGridEventArgs classes
using System.Windows;
using System.Windows.Controls;
namespace GridMeasureExample
{
class InfoGrid : Grid
{
protected override Size ArrangeOverride(Size arrangeSize)
{
CallReportInfoEvent("Arrange");
return base.ArrangeOverride(arrangeSize);
}
protected override Size MeasureOverride(Size constraint)
{
CallReportInfoEvent("Measure");
return base.MeasureOverride(constraint);
}
public event EventHandler<InfoGridEventArgs> ReportInfo;
private void CallReportInfoEvent(string message)
{
if (ReportInfo != null)
ReportInfo(this, new InfoGridEventArgs(message));
}
}
public class InfoGridEventArgs : EventArgs
{
private InfoGridEventArgs()
{
}
public InfoGridEventArgs(string message)
{
this.TimeStamp = DateTime.Now;
this.Message = message;
}
public DateTime TimeStamp
{
get;
private set;
}
public String Message
{
get;
private set;
}
}
}
Main Window XAML
<Window x:Class="GridMeasureExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GridMeasureExample"
Title="SharedSizeGroup" Height="500" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.IsSharedSizeScope="True">
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid1" Background="Green" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Blue" Grid.Row="0" Text="Row 0"/>
<TextBlock Background="Red" Grid.Row="1" Name="textBlock1" Height="100"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Blue" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid1}" />
</StackPanel>
<StackPanel Orientation="Vertical" Width="100">
<local:InfoGrid x:Name="grid2" Background="Yellow" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="15" />
<RowDefinition SharedSizeGroup="Group1" />
<RowDefinition Height="15" />
</Grid.RowDefinitions>
<TextBlock Background="Orange" Grid.Row="0" Text="Row 0" />
<TextBlock Background="Purple" Grid.Row="1" Name="textBlock2" Height="150"
Text="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"/>
<TextBlock Background="Orange" Grid.Row="2" Text="Row 2" />
</local:InfoGrid>
<TextBlock Text="{Binding Path=ActualHeight, ElementName=grid2}" />
</StackPanel>
</StackPanel>
<ListBox x:Name="lstInfo"
Grid.Column="1"
Grid.Row="0"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<UniformGrid Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Columns="2"
HorizontalAlignment="Center"
Margin="5">
<Button x:Name="btnIncrease" Margin="4,0">Increase</Button>
<Button x:Name="btnDecrease" Margin="4,0">Decrease</Button>
</UniformGrid>
</Grid>
</Window>
Main Window Constructor (only code in code-behind)
public Window1()
{
InitializeComponent();
btnIncrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Increase Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
textBlock2.Height += 30;
};
btnDecrease.Click += (s, e) =>
{
lstInfo.Items.Add(String.Format("{0} Decrease Button Pressed", DateTime.Now.ToString("HH:mm:ss.ffff")));
if (textBlock2.ActualHeight >= 30)
textBlock2.Height -= 30;
};
grid1.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Left Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
grid2.ReportInfo += (s, e) => lstInfo.Items.Add(String.Format("{0} Right Grid: {1}", e.TimeStamp.ToString("HH:mm:ss.ffff"), e.Message));
}

Categories

Resources