Set Dockpanel size in grid programmatically - c#

My goal is to programmatically set a DockPanel size.
I want it to span from Grid.Column=1, Grid.Row=1, Grid.RowSpan=5
And I know hot to set it statically in xaml, but not in c#.
Explanation to code: In xaml I made a 1row 1 column grid with some textfields and a button in the DockPanel. In when I press the button it should create a grid with as many column/rows as I wrote in the textfields. Then name each column and each row. And know I want to create a Dockpanel on some of these fields but for that I must define where it starts and how far it spans. This is where the problem is.
here is my xaml code how I made it:
<Grid Name="MainWindowGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="DockPanel"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="LightSalmon" Grid.Row="0" Grid.Column="0" Grid.RowSpan="8">
<StackPanel>
<TextBox Name="txtColums" Text="16"/>
<TextBox Name="txtRows" Text="8"/>
<TextBox Name="txtWindowHeight" Text="800"/>
<TextBox Name="txtWindowWidth" Text="1600"/>
<Button x:Name="ButtonCreate" Content="Create" Click="ButtonCreate_Click"/>
</StackPanel>
</DockPanel>
<ContentControl Content="{Binding}"/>
</Grid>
And my C# code what I have so far:
Methods Methods = new Methods();
Methods.CreateField(MainWindowGrid, txtColums, txtRows, txtWindowHeight, txtWindowWidth, MainWindow1);
int GridColumnCount = MainWindowGrid.ColumnDefinitions.Count;
int GridRowCount = MainWindowGrid.RowDefinitions.Count;
for (int a = 1; a < GridColumnCount; a++)
{
MainWindowGrid.ColumnDefinitions.ElementAt(a).Name = "C" + a;
}
for (int a = 1; a < GridRowCount; a++)
{
MainWindowGrid.RowDefinitions.ElementAt(a).Name = "R" + a;
}
var converter = new System.Windows.Media.BrushConverter();
var brush1 = (Brush)converter.ConvertFromString("#FFFFFFF0");
DockPanel myDockPanel = new DockPanel();
myDockPanel.Background = brush1;
myDockPanel.
At the very end I want to be able to set at which row/column the dockpanel should be and then span it, but I sadly do not know how.

You could use the following methods to set the Grid.Column, Grid.Row and Grid.RowSpan attached properties of myDockPanel:
Grid.SetColumn(myDockPanel, 1); //= <DockPanel ... Grid.Column = "1"
Grid.SetRow(myDockPanel, 1); //= <DockPanel ... Grid.Row = "1"
Grid.SetRowSpan(myDockPanel, 8); //= <DockPanel ... Grid.RowSpan = "8"

Related

How to add a XAML Resource (Grid with ItemsControls) to a FlowDocument in Code-Behind multiple times

I am working on a WPF Window that presents the results of some technical calculations inside a FlowDocumentViewer.
Problem: The FlowDocument and all its content is created from Code-Behind because every calculation differs a bit in terms of headers, shown results, and lines. I use different BlockUIContainers which hold a Resource Grid with some ItemsControls to show the results in a formatted order and add it to a Section and then to the Blocks of the Document, but only the last Block is shown inside the Reader.
I don't understand why after using FlowDoc.Blocks.Add(section) multiple times, only the last Block is shown.
I have created a Resource inside the XAML Code to fill it from Code Behind with the results. I need the output to look like this, e.g.:
M,ed = 70 kNm
Q,ed = 25 kN
N,ed = 30 kN
...
To achieve formatting like this, I created a grid with three columns, each containing an ItemsControl with a DataTemplate TextBlock, which has a Binding to a List<> of results.
<Window x:Class="FaceplateInput.Output"
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:FaceplateInput"
mc:Ignorable="d"
x:Name="OutputWnd"
WindowStartupLocation="CenterScreen"
Title="Output" Height="1000" Width="700">
<Window.Resources>
<Grid x:Key ="TestGrid" x:Name="Grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ItemsControl x:Name="ItemsLeft" Grid.Column="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="ItemsMid" Grid.Column="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="ItemsRight" Grid.Column="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window.Resources>
...
<FlowDocumentScrollViewer x:Name="DocReader" Grid.Column="1" Grid.Row="2" Margin="20" MaxWidth="700">
</FlowDocumentScrollViewer>
</Grid>
</Window>
I want to create the FlowDocument in Code-Behind to have control over formatting, especially because the document is created from a single string that is returned by the calculating class.
I build the following test-method to see how FlowDocuments work. The result is that only the added paragraphs and the last section are shown in the output window.
Code-Behind:
namespace FaceplateInput
{
public partial class Output: Window
{
public string PathToImage;
public string PathToBackground;
public string PathToTXTFile;
public Output()
{
InitializeComponent();
CreateFlowDocument();
}
private void CreateFlowDocument()
{
FlowDocument FlowDoc = new FlowDocument();
List<string> leftStr = new List<string>();
List<string> midStr = new List<string>();
List<string> rightStr = new List<string>();
//for Testing purposes, i create some weired data to fill the Lists
for (int i = 0; i < 10; i++)
{
leftStr.Add($"LeftLine {i + 1}");
midStr.Add("=");
rightStr.Add($"RightLine {i + 1}");
}
//Creating a new Container with Resource Grid as UIElement:
BlockUIContainer cont = new BlockUIContainer((UIElement)this.FindResource("TestGrid"));
Grid child = (Grid)cont.Child;
//setting the sources for the ItemsControl:
ItemsControl items1 = (ItemsControl)child.Children[0];
items1.ItemsSource = leftStr;
ItemsControl items2 = (ItemsControl)child.Children[1];
items2.ItemsSource = midStr;
ItemsControl items3 = (ItemsControl)child.Children[2];
items3.ItemsSource = rightStr;
//adding section holding BlockUIContainer to Document
Section section = new Section();
section.Blocks.Add(cont);
FlowDoc.Blocks.Add(section);
//disconnecting UIContainer from parent to avoid Exception
section.RemoveChild(cont.Child);
cont.Child = null;
//another "testline" to see where it puts it in the document
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 1\n")));
//all the stuff above again to test
leftStr.Clear();
midStr.Clear();
rightStr.Clear();
leftStr.Add("______");
midStr.Add("_____________________");
rightStr.Add("_____________________");
for (int i = 0; i < 20; i++)
{
leftStr.Add($"Left: {(double)i * 324 / 10}\n");
midStr.Add("=\n");
rightStr.Add($"{(double)i * 13 / 2}\n");
}
BlockUIContainer cont1 = new BlockUIContainer((UIElement)this.FindResource("TestGrid"));
Grid child1 = (Grid)cont1.Child;
items1 = (ItemsControl)child1.Children[0];
items1.ItemsSource = leftStr;
items2 = (ItemsControl)child1.Children[1];
items2.ItemsSource = midStr;
items3 = (ItemsControl)child1.Children[2];
items3.ItemsSource = rightStr;
Section section1 = new Section();
section1.Blocks.Add(cont1);
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 2\n")));
FlowDoc.Blocks.Add(section1);
FlowDoc.Blocks.Add(new Paragraph(new Run("TestString 3\n")));
DocReader.Document = FlowDoc;
}
//...
}
}
I'd appreciate any tips on how to get this working, and maybe how to improve the code itself, or any ideas for completely different approaches.
Greets and thx, Crawliiee
You are setting the first container to null, so that first section is empty. Remove this line:
cont.Child = null;
An alternative could be to use a ListView with GroupView as in the example
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.listview?view=netframework-4.7.2&f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Windows.Controls.ListView);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue

Cannot dynamically create columns/rows in WPF

I want to be able to dynamically set a window's size in WPF, and then create as many columns and rows in a grid as I want.
I've created a method for it but it does not seem to work. I also added a border in XAML to see if there are columns and rows but I only see one square.
Also, it throws no error whatsoever.
This is my method:
public void CreateField(Grid MainGrid, TextBox Columns, TextBox Rows, TextBox WHeight, TextBox WWidth, MainWindow MainWindow)
{
int ColumnCount = Int32.Parse(Columns.Text);
int RowCount = Int32.Parse(Rows.Text);
int WindowHeight = Int32.Parse(WHeight.Text);
int WindowWidth = Int32.Parse(WWidth.Text);
MainWindow.MainWindow1.Height = WindowHeight;
MainWindow.MainWindow1.Width = WindowWidth;
for(int a = 0; a <= ColumnCount; a++){
ColumnDefinition c = new ColumnDefinition();
c.Width = new GridLength(WindowWidth / ColumnCount, GridUnitType.Pixel);
MainGrid.ColumnDefinitions.Add(c);
}
for (int a = 0; a <= RowCount; a++)
{
RowDefinition r = new RowDefinition();
r.Height = new GridLength(WindowHeight / RowCount, GridUnitType.Pixel);
MainGrid.RowDefinitions.Add(r);
}
}
In XAML I have this good with only 1 column and 1 row and a dockpanel for the textboxes and buttons.
<Border BorderBrush="Black" BorderThickness="2">
<Grid Name="MainWindowGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="DockPanel"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Background="LightSalmon" Grid.Row="0" Grid.Column="0" Grid.RowSpan="8">
<StackPanel>
<TextBox Name="txtColums" Text="16"/>
<TextBox Name="txtRows" Text="8"/>
<TextBox Name="txtWindowHeight" Text="800"/>
<TextBox Name="txtWindowWidth" Text="1600"/>
<Button x:Name="ButtonCreate" Content="Create" Click="ButtonCreate_Click"/>
</StackPanel>
</DockPanel>
<ContentControl Content="{Binding}"/>
</Grid>
</Border>
And according to the parameters written here the method should be executed on button click. But instead of a grid with 16 columns and 8 rows, I only get 1 column 1 row. (you can see the border at the edge)
So what am I doing wrong here? I have no real experience with grids whatsoever, and I am pretty clueless. Hope someone can help me out.
EDIT:
The suggestion of to activate MainGrid.ShowGridLines as said by ASh worked. Did not know about this functionality. As it turns out i do have succesfully created the columns and rows. I thought it not to work because i tried to paint a field in the grid with a color which did not work. Now i wonder, why this does not work as i thought this to be correct code.
var converter = new System.Windows.Media.BrushConverter();
var brush1 = (Brush)converter.ConvertFromString("#FFFFFFF0");
DockPanel myDockPanel = new DockPanel();
Grid.SetColumn(myDockPanel, 3);
Grid.SetRow(myDockPanel, 3);
myDockPanel.Background = brush1;
There was a lot going wrong here and its a magical jar of wonderment why you wanted to do this, however this should point you in a better direction
Some Modifications
Dedicated Grid
Get the ActualHeight and ActualWidth to use
Set GridLines true, so you can see whats happening
Set the Grid alignments to Stretch
Don't alter the size of the window
Xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Grid.Column="0"
BorderBrush="Black"
BorderThickness="2">
<DockPanel Grid.RowSpan="8" Background="LightSalmon">
<StackPanel>
<TextBox Name="txtColums" Text="16" />
<TextBox Name="txtRows" Text="8" />
<TextBox Name="txtWindowHeight" Text="800" />
<TextBox Name="txtWindowWidth" Text="1600" />
<Button x:Name="ButtonCreate"
Click="ButtonCreate_OnClick"
Content="Create" />
</StackPanel>
</DockPanel>
</Border>
<Grid Name="MainWindowGrid"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ShowGridLines="True" />
</Grid>
Code behind
public void CreateField(Grid MainGrid, TextBox Columns, TextBox Rows) // TextBox WHeight, TextBox WWidth, MainWindow MainWindow)
{
var ColumnCount = int.Parse(Columns.Text);
var RowCount = int.Parse(Rows.Text);
var width = MainGrid.ActualWidth;
var height = MainGrid.ActualHeight;
for (var a = 0; a <= ColumnCount; a++)
{
var c = new ColumnDefinition();
c.Width = new GridLength(width / ColumnCount, GridUnitType.Pixel);
MainGrid.ColumnDefinitions.Add(c);
}
for (var a = 0; a <= RowCount; a++)
{
var r = new RowDefinition();
r.Height = new GridLength(height / RowCount, GridUnitType.Pixel);
MainGrid.RowDefinitions.Add(r);
}
}
private void ButtonCreate_OnClick(object sender, RoutedEventArgs e)
{
CreateField(MainWindowGrid, txtColums, txtRows);
}
grid layout is correct. set MainGrid.ShowGridLines=true; to see it
to see dynamically created control you should add it to grid:
MainGrid.Children.Add(myDockPanel);
since grid rows have equal height and columns have equal width, the following lines can be safely removed:
r.Height = new GridLength(WindowHeight / RowCount, GridUnitType.Pixel);
c.Width = new GridLength(WindowWidth / ColumnCount, GridUnitType.Pixel);
if Width/Height is not set, it is defaulted to * which means Grid will size them equally.
as an alternative UniformGrid can be used:
AnotherMainGrid = new UnifromGrid {Rows = RowCount, Columns = ColumnCount };
but in this case child elements must be added consequtively

Accessing grid elements programmatically

Let's say I have:
<Grid Name="paramGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="0"/>
</Grid>
I am aware of how to add rows/columns such as:
paramGrid.RowDefinitions.Add(new RowDefinition());
TextBox tb = new TextBox();
tb.Text = "Sample";
tb.Name = "textBox";
paramGrid.Children.Add(tb);
Grid.SetColumn(tb, 0);
Grid.SetRow(tb, 2);
The above adds a TextBox to the new row.
My question is: How would I go about accessing it now? I need to query the .Text property of the TextBox on the new row.
Keep a ref to the textbox:
private TextBox m_Tb;
...
m_Tb = new TextBox();
m_Tb.Text = "Sample";
m_Tb.Name = "textBox";
....
something something = m_Tb.Text;
Find it in the grid's Children collection:
var tb = (TextBox)paramGrid.Children[0];
something something = tb.Text;
Obviously [0] will only work if the text box is either the only child in the grid or the first one.

Presenting list of steps

I am working on Windows 8 application in C#/XAML.
I have a list of steps to show and the list can have one to many steps.
I have tried the GridView and ListView controls, but with those, it is not possible to have each element have its own height (because one step might have only one line of text, and the next one 3 lines, for example). The VariableSizedGridview does not help either.
What I am trying to achieve is something like the way cooking steps are shown in the Microsoft Bing Food & Drink app. So, steps are shown in rows in the first column, and when the end of the page is reached, it creates a second column, and so on. Like so :
Could anyone please help me find a way to achieve this?
What control to use and how?
It looks very simple, but I was not able to find any solution while searching online.
Thank you
Here is what I have done with the Gridview control (the Listview was quite similar) :
<Grid Name="gridSteps" Grid.Column="3" Margin="25,69,25,69">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="ÉTAPES" FontSize="22" FontWeight="Bold"></TextBlock>
<GridView Grid.Row="1" Name="gvGroupSteps" SelectionMode="None" IsHitTestVisible="False" VerticalAlignment="Top">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Width="400">
<TextBlock Text="{Binding Order}" Margin="0,15,0,0" FontSize="20" Foreground="Bisque"></TextBlock>
<TextBlock Text="{Binding Description}" Margin="0,5,0,0" FontSize="18" TextWrapping="Wrap"></TextBlock>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Background="#FFC9C9C9">
<TextBlock Text="{Binding GroupName}" FontSize="20" FontWeight="SemiBold"></TextBlock>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</Grid>
You may want to post the XAML that you have tried. It sounds like to me that you need to nest your view items. Consider this very simple example:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ListView>
<ListViewItem>Step 1</ListViewItem>
<ListViewItem>
<ListView>
<ListViewItem>Step 1a</ListViewItem>
<ListViewItem>Step 1b</ListViewItem>
<ListViewItem>Step 1c</ListViewItem>
</ListView>
</ListViewItem>
<ListViewItem>Step 2</ListViewItem>
</ListView>
</Grid>
I have tried the GridView and ListView controls, but with those, it is not possible to have each element have its own height
My recollection is that you can in fact have elements with different heights using those controls. These are both types of ItemsControl, which supports data templating, which in turn allows you to customize the appearance of each item, including its height.
That said, you may find that the simpler ListBox suits your needs in this case. It's hard to say without a code example or other details.
You should read MSDN's Data Templating Overview, which has a thorough discussion of the whole process, along with some good examples of what you can do. Pay particular attention to the section named "Choosing a DataTemplate Based on Properties of the Data Object". While a single template could still have variable height, clearly by using a different template according to your specific needs you can customize each item's style to your heart's content.
If this does not address your question, please provide a more detailed question. You should include a good, minimal, complete code example that shows clearly what you've tried, explaining precisely what that code does and how that's different from what you want it to do.
I have been looking all over the internet for a solution, but could not manage to find anything.
So i decided to do everything myself in C# code.
In short, in have a StackPanel with Orientation set to Horizontal, and I add a Grid to it and add rows to that Grid for every item i have. When the maximum height is reached (based on the screen Height), I add a new Grid to the StackPanel, and so on.
Here is my code if anyone needs it :
// Nombre de lignes maximal (16 lignes à 1080p)
int maxCharCount = (int)Window.Current.Bounds.Height * 16 / 1080;
spIngredients.Children.Clear();
foreach (var groupIngredient in db.Table<GroupIngredient>().Where(x => x.RecipeId == _currentRecipe.Id))
{
int linesCount = 0;
int row = 0;
var gGroup = new Grid();
spIngredients.Children.Add(gGroup);
gGroup.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var groupName = new TextBlock() { Text = groupIngredient.Name, FontSize = 20, FontWeight = FontWeights.SemiBold, Margin = new Thickness(10) };
gGroup.Children.Add(groupName);
Grid.SetRow(groupName, row);
foreach (var ingredient in db.Table<Ingredient>().Where(x => x.GroupIngredientId == groupIngredient.Id))
{
// Nombre de lignes, split à 45 char
linesCount += 1 + ingredient.IngredientFull.Length / 45;
if (linesCount >= maxCharCount)
{
var gCol = new Grid();
spIngredients.Children.Add(gCol);
gCol.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var col = new TextBlock() { Text = "", FontSize = 20, FontWeight = FontWeights.SemiBold, Margin = new Thickness(10) };
gCol.Children.Add(col);
gGroup = gCol;
row = 0;
linesCount = 0;
Grid.SetRow(col, row);
}
row++;
ingredient.Quantity = ingredient.Quantity * multiplier;
gGroup.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var ingredientName = new TextBlock() { Text = ingredient.IngredientFull, Margin = new Thickness(10), FontSize = 18, TextWrapping = TextWrapping.Wrap, MaxWidth = 300 };
gGroup.Children.Add(ingredientName);
Grid.SetRow(ingredientName, row);
}
}

Dynamic Chat Window

I have a problem with the performance of the wpf gui.
At first I will explain what I have done.
I read from a Database different chat data, mostly text but sometimes there is an icon in the middle of the text, like a smiley or similar. Or, there are no text just a Image.
I have this all done by using a Flowdocument and use a Textblock with inlines. Oh I forgot, I use wpf, sorry.
Thats work great, BUT at the moment the Flowdocument will be painted to the RichTextbox or FlowdocumentReader, its take a long time and the gui freeze. I have think about Virtualizing but a RichTextBox doesn't use this. So my next idea was to use a Listbox and set as item a Richtextbox for every Chatbubble. A Chat can contain round about 20.000 Chatbubbles.
So now I want to use Databinding but I doesn't find a way to bind the inlines of a Textblock.
So now some code.
<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
<Grid>
<RichTextBox x:Name="rtbChat"
SpellCheck.IsEnabled="False"
VerticalScrollBarVisibility="Auto"
VerticalContentAlignment="Stretch">
<FlowDocument
FontFamily="Century Gothic"
FontSize="12"
FontStretch="UltraExpanded">
<Paragraph>
<Figure>
<BlockUIContainer>
<Border>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="tUser"
Foreground="Gray"
TextAlignment="Right"
FontSize="10"
Grid.Row="0"
Grid.Column="1"
Text="{Binding displayUserName}"/>
<TextBlock x:Name="tTime"
Foreground="Gray"
TextAlignment="Left"
FontSize="10"
Grid.Row="0"
Grid.Column="0"
Text="{Binding sendTime}"/>
<TextBlock x:Name="tMessage"
Foreground="Black"
TextAlignment="Justify"
FontSize="12"
Height="NaN"
TextWrapping="Wrap"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Text="{Binding contentText}" />
<Image x:Name="tImage"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Height="NaN"
Source="{Binding imageSend}"/>
</Grid>
</Border>
</Border>
</BlockUIContainer>
</Figure>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
</DataTemplate>
So this is not final, I'm porting this from Source-code to xaml and some setters are missing at this moment.
I have benchmark the timings and everything works fine, 10 ms for the sqlite, round about 4 sec for the building of the FlowDocument but up to 5 min to paint the FlowDocument in the RichTextBox. I know that is why the hole box is painted, also the part that is not visible.
I hope that is understandable, if not ask me :)
Here the Source-Code before ported to xaml.
var rtBox = new RichTextBox
{
//IsEnabled = false,
BorderThickness = new Thickness(0, 0, 0, 0)
};
var doc = new FlowDocument();
Contact contact = null;
contact = _mess.remote_resource != "" ? _contacts.Find(x => x._jid == _mess.remote_resource) : _contacts.Find(x => x._jid == _mess.key_remote_jid);
var para = new Paragraph();
//--- Style of the message -----
para.Padding = new Thickness(0);
BlockUIContainer blockUI = new BlockUIContainer();
blockUI.Margin = new Thickness(0, 0, 0, 0);
blockUI.Padding = new Thickness(0);
blockUI.TextAlignment = _mess.key_from_me == 1 ? TextAlignment.Right : TextAlignment.Left;
Border bShadow = new Border();
bShadow.Width = 231;
bShadow.BorderBrush = Brushes.LightGray;
bShadow.BorderThickness = new Thickness(0, 0, 0, 1);
Border b2 = new Border();
b2.Width = 230;
b2.BorderBrush = Brushes.Gray;
b2.Background = Brushes.White;
b2.BorderThickness = new Thickness(0.5);
b2.Padding = new Thickness(2);
Grid g = new Grid();
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(150,GridUnitType.Star) });
g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(80) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(15) });
g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25,GridUnitType.Auto) });
TextBlock tUser = new TextBlock()
{
Foreground = Brushes.Gray,
TextAlignment = TextAlignment.Right,
FontSize = 10,
};
tUser.SetValue(Grid.RowProperty, 0);
tUser.SetValue(Grid.ColumnProperty, 1);
if(contact != null)
tUser.Text = _mess.key_from_me == 1 ? "ich" : (contact._displayName == "" ? Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource) : contact._displayName);
else
{
tUser.Text = Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource);
}
TextBlock tTime = new TextBlock()
{
Foreground = Brushes.Gray,
TextAlignment = TextAlignment.Left,
FontSize = 10,
};
tTime.SetValue(Grid.RowProperty, 0);
tTime.SetValue(Grid.ColumnProperty, 0);
tTime.Text = UnixTime.TimeReturnUnix2DateUtc(_mess.timestamp, timeZone).ToString();
TextBlock tMessage = new TextBlock()
{
Foreground = Brushes.Black,
TextAlignment = TextAlignment.Justify,
FontSize = 12,
Height = Double.NaN,
TextWrapping = TextWrapping.Wrap
};
tMessage.SetValue(Grid.RowProperty, 1);
tMessage.SetValue(Grid.ColumnProperty, 0);
tMessage.SetValue(Grid.ColumnSpanProperty, 2);
for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
{
var x = Char.ConvertToUtf32(_mess.data, i);
if (EmojiConverter.EmojiDictionary.ContainsKey(x))
{
//Generate new Image from Emoji
var emoticonImage = new Image
{
Width = 20,
Height = 20,
Margin = new Thickness(0, -5, 0, -5),
Source = EmojiConverter.EmojiDictionary[x]
};
//add grafik to FlowDocument
tMessage.Inlines.Add(emoticonImage);
}
else
{
tMessage.Inlines.Add(new Run("" + _mess.data[i]));
}
}
g.Children.Add(tUser);
g.Children.Add(tTime);
g.Children.Add(tMessage);
b2.Child = g;
bShadow.Child = b2;
blockUI.Child = bShadow;
Figure fig = new Figure(blockUI);
fig.Padding = new Thickness(0);
fig.Margin = new Thickness(0);
fig.Height = new FigureLength(0, FigureUnitType.Auto);
para.Inlines.Add(fig);
doc.Blocks.Add(para);
rtBox.Document = doc;
msgList.Add(rtBox);
Greetings and thanks for your help.
One method would be to virtualize using a ListBox, certainly. Arguably better methods would be to dynamically load in the required messages or make your own virtualized control (issues with the default ListBox virtualization include that you have to scroll entire items in a single go to get virtualization working... which can suck a bit from a UX perspective in some cases.)
From the sound of it still taking forever to load, the virtualization you've set up isn't working right...
The main thing that you require to get virtualization working is that you need to have the ScrollViewer inside the ListBox template have CanContentScroll=True. Ie do:
<ListBox ScrollViewer.CanContentScroll="True" .... >
Or give the ListBox a template similar to below:
<ControlTemplate>
<Border BorderBrush="{TemplateBinding Border.BorderBrush}"
BorderThickness="{TemplateBinding Border.BorderThickness}"
Background="{TemplateBinding Panel.Background}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}">
<ScrollViewer Focusable="False"
Padding="{TemplateBinding Control.Padding}"
MaxHeight="{TemplateBinding Control.MaxHeight}"
CanContentScroll="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
Also, unless you want to actually select previous messages, maybe a ListBox isn't what you want, and you actually want an ItemsControl? See Virtualizing an ItemsControl? for more on that.
Addition 1 - Smooth Scrolling + Virtualization:
See below - if you also want smooth scrolling, might be worth looking at a TreeView - see http://classpattern.com/smooth-scrolling-with-virtualization-wpf-list.html#.VBHWtfldXSg - though I can't vouch for if this actually works at the moment, just discovered it myself!
Addition 2 - Clarification RE needed elements
As in my comments below, if you're not editing everything, you can get rid of all the tags:
<Grid><RichTextBox><FlowDocument><Paragraph><Figure>
In the data template. You probably can't bind the Text of the message to the contentText in the DataTemplate, and will have to have a bit of behind-the-scenes code to dynamically generate the inlines for the TextBlock.
Addition 3 - How to bind a TextBlock to contain images etc from XAML
Okay, so overall (neglecting some styling), I suggest the following:
<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
<Border>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="tUser"
Foreground="Gray"
TextAlignment="Right"
FontSize="10"
Grid.Row="0"
Grid.Column="1"
Text="{Binding displayUserName}" />
<TextBlock x:Name="tTime"
Foreground="Gray"
TextAlignment="Left"
FontSize="10"
Grid.Row="0"
Grid.Column="0"
Text="{Binding sendTime}" />
<TextBlock x:Name="tMessage"
Foreground="Black"
TextAlignment="Justify"
FontSize="12"
Height="NaN"
TextWrapping="Wrap"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}" />
<Image x:Name="tImage"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Height="NaN"
Source="{Binding imageSend}" />
</Grid>
</Border>
</Border>
</DataTemplate>
Note the line classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}" on the message TextBlock. This is in order to be able to bind to Inlines... Basically, this not a dependency property, so cannot be directly bound to!
Instead, we can use the custom static class TextBlockInlineBinder below to create a static dependency property to add to our TextBlock, which when it is updated, it runs the InlinesChanged method to update the Inlines:
public static class TextBlockInlineBinder
{
#region Static DependencyProperty Implementation
public static readonly DependencyProperty InlinesProperty =
DependencyProperty.RegisterAttached("Inlines",
typeof(IEnumerable<Inline>),
typeof(TextBlockInlineBinder),
new UIPropertyMetadata(new Inline[0], InlinesChanged));
public static string GetInlines(DependencyObject obj)
{
return (string)obj.GetValue(InlinesProperty);
}
public static void SetInlines(DependencyObject obj, string value)
{
obj.SetValue(InlinesProperty, value);
}
#endregion
private static void InlinesChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var value = e.NewValue as IEnumerable<Inline>;
var textBlock = sender as TextBlock;
textBlock.Inlines.Clear();
textBlock.Inlines.AddRange(value);
}
}
Finally, the binding (which I've bound to a contentInlines property on your Message class) will need to be of type IEnumerable<Inline>, ie something like:
public IEnumerable<Inline> contentInlines
{
get {
var inlines = new List<Inline>();
for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
{
var x = Char.ConvertToUtf32(_mess.data, i);
if (EmojiConverter.EmojiDictionary.ContainsKey(x))
{
//Generate new Image from Emoji
var emoticonImage = new Image
{
Width = 20,
Height = 20,
Margin = new Thickness(0, -5, 0, -5),
Source = EmojiConverter.EmojiDictionary[x]
};
inlines.Add(emoticonImage);
}
else
{
inlines.Add(new Run("" + _mess.data[i]));
}
}
return inlines;
}
}

Categories