Presenting list of steps - c#

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);
}
}

Related

XAML change TextBlock Margin property based on Rectangle's size

I'm trying to create something looking like this :
It's designed to be an XAML title for VMIX software, video broadcasting purposes.
I'm gonna get a lot of datas from a GSheet, handle in VMIX, and assign those datas to my TextBlocks such as "Candidate", "City" and the Votes %.
From that % I want the bar size to increase/decrease, I managed to do part of that.
But the main issue is to get the % TextBlock margin to fit on the right of the rectangle.
Anyone knows how I could do that ?
I have never been coding in C#, I have a background in C, C++ and JS, so I've spent my day looking for that purpose and couldn't make it right.
I saw some binding methods that could fit, but I'm unable to use them.
Moreover I'm working on Blend for Visual Studio 2017, and I don't get why I can't run some simple code on it when pressing F5... It's another problem thought.
Thanks a lot for your help.
EDIT :
I've reached something new so far, really DIY solution but it's my lsat solution if I can't find better :
I'll have 2 TextBlock for 1 ProgressBar (Thanks to Chris)
<Grid Margin="0,0,-8,0">
<TextBlock x:Name="Votes1" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Margin="{Binding Text, ElementName=MarginVotes1}" FontSize="72" Width="853" Height="188"><Run Text="6"/><Run Text="00"/></TextBlock>
<ProgressBar HorizontalAlignment="Left" Height="79" Margin="171,503,0,0" VerticalAlignment="Top" Width="{Binding Path=Text, ElementName=Votes1}" Background="#FFEA4545"/>
<TextBlock x:Name="MarginVotes1" HorizontalAlignment="Left" Margin="171,587,0,0" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="72" Height="98" Width="550"><Run Text="8"/><Run Text="0"/><Run Text="0"/><Run Text=","/><Run Text="4"/><Run Text="9"/><Run Text="0"/><Run Text=",0,0"/>
</TextBlock>
So this works fine, but I have to prepare before what my "MarginVotes1" value is (in GoogleSheet).
The best would be directly in code behind to do something like this :
CONVERT Votes1.Text to Int STORE in val
SET x to val + DefaultMargin
CONVERT x to String STORE in MarginX
CREATE String MarginVoteStr as MarginX + ",500, 0, 0"
SET Votes1.Margin as MarginVoteStr
Welcome to WPF. Here's some code I put together that you should be pretty close to what you need.
XAML:
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding Candidates}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Candidate" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}"/>
<Rectangle Grid.Column="1" Height="10" Margin="5, 0" Width="{Binding BarWidth}" Fill="{Binding BarColor}"/>
<TextBlock Grid.Column="2" Text="{Binding Percentage, StringFormat=P}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Candidates = new List<Candidate> { new Candidate { Name = "Joe", Percentage = .50, BarColor = Brushes.Green},
new Candidate { Name = "Bob", Percentage = .30, BarColor = Brushes.Yellow},
new Candidate { Name = "Sarah", Percentage = .20, BarColor = Brushes.Gray}};
}
public List<Candidate> Candidates
{
get { return (List<Candidate>)GetValue(CandidatesProperty); }
set { SetValue(CandidatesProperty, value); }
}
public static readonly DependencyProperty CandidatesProperty =
DependencyProperty.Register("Candidates", typeof(List<Candidate>), typeof(MainWindow));
}
public class Candidate
{
public string Name { get; set; }
public double Percentage { get; set; }
public Brush BarColor { get; set; }
//This is just shorter syntax for a readonly property.
//The multiplier (200) should be whatever length you want a full bar to be
public double BarWidth => Percentage * 200;
}
There are a number of points you should note:
ItemsControl and DataTemplate
Whenever you need to display multiple data items in WPF, especially if the number of items is variable, you should be using some type of ItemsControl.
An ItemsControl takes a collection of some kind and displays each item using a DataTemplate. An ItemsControl creates a new instance of its ItemTemplate for every item in its source collection. The link between the data and the visuals is established through data bindings.
Layout
Everything between the <DataTemplate> tags is the visual layout of a single item.
Notice that I am not using Margin to create the desired layout. Instead of using Margin in that way, I'm using one of WPFs many Panel controls: Grid. With Grid you can define rows and columns like a table.
Each item in my example is a Grid with 1 row and 3 columns. The elements that make up the item are placed in that grid using the Grid.Column property. Each column has Width="Auto", which means it will grow to accommodate the width of what's inside. IsSharedSizeScope and SharedSizeGroup make it so that the Grids of each individual item all have the same width for the first column.
Candidate class
This is the class that will be used to store and represent the data being displayed. Note that the property names match the {Binding ______} values from the DataTemplate.
My example main window has a collection of Candidate objects stored in a dependency property. This property is bound to the ItemsSource of the ItemsControl.
Overall
The idea is to populate your collection with whatever data items you need and let the ItemsControl take care of the rest, thus keeping the data and visuals of the project relatively independent. Even small visual aspects like formatting the percentage value correctly for display can be done using the DataTemplate instead of writing the code in C#, as shown using StringFormat=P.

Set Dockpanel size in grid programmatically

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"

How to dynamically create horizontal scrollable Gridview inside verticallly scollable page at Runtime

I want to create a page like Android playstore in which i have to create multiple horizontal scrollable Gridviews on the basis of data at runtime. As i am new to windows phone development i don't know how to create it dynamically. So Please provide any type of help or tutorial regarding this.
I have implemented the below code with this code i am able to produce the required result but the gridview items are not stacked horizontally.I want to make the items scroll horizontally So please provide any help with which required result can be achieved.I am attaching a screenshot for reference.
public void DesignUi()
{
GridViewItem grdItem = new GridViewItem();
for (int i = 0; i < 20; i++)
{
string backgroundColor = string.Empty;
StackPanel staParent = new StackPanel();
#region Header
StackPanel headerStack = new StackPanel();
headerStack.Background = new SolidColorBrush(Colors.Pink);
TextBlock textHeader = new TextBlock();
textHeader.Text = "Header :-" + i;
headerStack.Children.Add(textHeader);
#endregion
#region Body
StackPanel staBody = new StackPanel();
staBody.Background = new SolidColorBrush(Colors.Green);
#region Create Grid View
GridView grd = new GridView();
grd.SetValue(ScrollViewer.VerticalScrollModeProperty, ScrollMode.Disabled);
grd.SetValue(ScrollViewer.HorizontalScrollModeProperty, ScrollMode.Enabled);
ItemsPanelTemplate itmPanel = new ItemsPanelTemplate();
VirtualizingStackPanel vrStack = new VirtualizingStackPanel();
vrStack.Orientation = Orientation.Horizontal;
TextBlock textQ = new TextBlock();
textQ.Text = "";
vrStack.Children.Add(textQ);
itmPanel.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, true);
itmPanel.SetValue(VirtualizingStackPanel.OrientationProperty, Orientation.Horizontal);
itmPanel.SetValue(ItemsControl.ItemContainerStyleProperty, Orientation.Horizontal);
ItemsControl itmCntrl = new ItemsControl();
itmCntrl.Items.Add(vrStack);
#region Create Gridview Items
for (int j = 0; j < 4; j++)
{
grdItem = new GridViewItem();
grdItem.Width = 100;
grdItem.Height = 150;
grdItem.Margin = new Thickness(5, 5, 5, 5);
grdItem.Background = new SolidColorBrush(Colors.Red);
TextBlock textGrd = new TextBlock();
textGrd.Text = "Item :-" + j;
grdItem.Content = textGrd;
grd.Items.Add(grdItem);
}
#endregion
#endregion
staBody.Children.Add(grd);
#endregion
staParent.Children.Add(headerStack);
staParent.Children.Add(staBody);
staLists.Children.Add(staParent);
}
}
Current Result Screenshot with the above code:---
Required Result Screenshot
I had to put this as an answer because the comment section was too limiting for this. So I better answer this and guide you towards the correct approach
OKay! there are better ways to do this. The best way and the easiest is via DataBinding. It'll reduce your code to almost nothing, and it'll be easier for you to design your GridView in XAML rather than doing it via c#. if you are not familiar with the concept of data binding and you want to implement it the way you're doing it now then I'll add to your solution that, the GridView would stack horizontally by setting the ItemsWrapGrid.Orientation property of your gridview to vertical to stack your elements horizontally and remember to set the scroll mode to horizontal too.
For the scroll mode: add the below to your GridView XAML
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.VerticalScrollMode="Disabled"
For setting the ItemsWrapGrid Orientation Property:
string template =
"<ItemsPanelTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"><ItemsWrapGrid VerticalAlignment = \"Top\" "
+ " ItemWidth = \""
+ itemWidth
+ "\" Orientation = \"Vertical\"/></ItemsPanelTemplate> ";
yourgridview.ItemsPanel = (ItemsPanelTemplate)XamlReader.Load(template);
Please Note:
The Better and cleaner way to achieve this would be via DataBinding, The Below is the code for achieving this via DataBinding:
The XAML
<GridView Name="ViewView" HorizontalAlignment="Center" ItemTemplate="{StaticResource AllAppsTileData}" IsItemClickEnabled="True" SelectionMode="Single" Margin="10" ItemsSource="{Binding AppsToShow}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.VerticalScrollMode="Disabled">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Vertical"/> <--Change this to Horizontal for vertically wrapping the items-->
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="5"/>
</Style>
</GridView.ItemContainerStyle>
</GridView>
The DataTemplate
To be defined in your <Page.Resources>
<DataTemplate x:Key="AllAppsTileData">
<Grid>
<Grid.Background>
<ImageBrush Stretch="Fill" ImageSource="{Binding AppImage}"/>
</Grid.Background>
<Grid>
<Grid.Background>
<SolidColorBrush Color="Black" Opacity="0.3"/>
</Grid.Background>
<TextBlock Text="{Binding AppName}" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Grid>
</DataTemplate>
Your backing app class
public class AppDataClass
{
public string AppName { get; set; }
public string AppImage { get; set; } //The image would be something like ms-appx:///Assets/LOGO.png
}
Now that you have your architecture ready, there are two ways you can go about it from here,
You bind the ItemsSource property of the GridView to an ObservableCollection<AppDataClass> which can be populated by your code behind or preferably a ViewModel using the MVVM approach and each time the ObservableCollection<AppDataClass> changes, it raises the RasiePropertyChanged event from the interface INotifyPropertyChanged and the view automatically updates itself. This is a highly recommended approach as it keeps your UI and Business Logic on two different threads and either one of them would not interact with each other, they'll get the data via the ViewModel, this is the MVVM approach for more information on it use This article
As you explained that you're new to the Phone development, I would say forget all about the 1st point because it can be tough to grasp if you're new to the platform, what i'll recommend is the easy way,
From your code behind get the data into a List something like this,
List<AppDataClass> MyEntireData = new List<AppDataClass>();
MyEntireData = GetData();
where the GetData method is returning you a List<AppDataClass> and now simply after the MyEntireData is not empty or it's count is > 0 use, ViewView.ItemsSource = MyEntireData;
And you'll have a much more organized code which provides you the store way kinda layout.
And in future if you want to change the way the Tiles look you don't need to wrap your head to the c# generated XAML, you just need to modify the DataTemplate.
If there is anything do let me know in the comments section.

How to set CheckBox margin dynamically in Silverlight

I am creating CheckBoxes dynamically on my grid. Now I also want to set the margin(respected to the CheckBox) dynamically but not sure what would be the best way.
For example,
If there is only one CheckBox then the margin should be {5,0,0,0}
If there are two CheckBoxes then the first should have {5,0,0,0} and second should have {10,0,0,0} and so on.
I am concern with left margin. These check box creation is based on List<String>.
XAML:
<Grid x:Name="SynonymsGrid" Grid.Column="2" Margin="0,35,0,0" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
CODE-BEHIND:
List<String> names = new List<string>() { "one", "two", "three" };
foreach (var name in names)
{
CheckBox chb = new CheckBox();
chb.Content = name;
chb.Margin = new System.Windows.Thickness { Left = 5, Top = 0, Right = 0, Bottom = 0 };
synonymsGrid.Children.Add(chb);
}
Above code will set all check boxes at one location which is obvious. I thought of using for loop but not sure what is the best approach.
Solution :
for (int i = 0; i < names.Count; i++)
{
CheckBox chb = new CheckBox();
chb.Content = names[i];
chb.Margin = new System.Windows.Thickness { Left = i * 150, Top = 0, Right = 0, Bottom = 0 };
synonymsGrid.Children.Add(chb);
}
Why do all this visual GUI operations in the code behind when there are Xaml constructs/controls that do the same thing?
To do this dynamically in Xaml...here are the steps.
First off, the StackPanel can evenly space out the checkboxes in a horizontal fashion instead of using a grid then margin'ing the controls to the right location. Note, you could even stop right here and replace your grid with a StackPanel and not even have a need for this question
But what makes it data driven (dynamic) in the Xaml paradigm, one uses the ItemsControl which specifies a panel or an area which will be our horizontal space (StackPanel) that will hold the dynamic items. Each of those dynamic items will be derived from a template containing a checkbox.
For example to achieve the three checkboxes based off of your Names list uses this :
Simply bind your list of names to ItemsControl which uses the StackPanel and a CheckBox using this Xaml:
<ItemsControl ItemsSource="{Binding Names}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"
Margin="5,0,0,0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

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