Silverlight Measure Method not working properly depending of databound object? - silverlight

UPDATE II
Problem was solved. Thank you.
For a simple Silverlight printing preview engine, my XAML looks like this (excerpt):
<Grid>
<TextBlock Text="{Binding IntroText}" />
<ItemsControl ItemsSource="{Binding DataItems}"
x:Name="DataItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
TextWrapping="Wrap"
Margin="0,2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding OutroText}" />
</Grid>
I want to ensure that everything fits on a page, therefore i have a simple method:
public bool FitsOnPrintPage(Size pageDimensions)
{
Measure(new Size(pageDimensions.Width, Double.PositiveInfinity));
return
DesiredSize.Height <= pageDimensions.Height &&
DesiredSize.Width <= pageDimensions.Width;
}
Now we have a strange problem here which I can't explain:
The bound collection DataItems is a generic object List. When containing simple strings, the Measure(...) method works as expected and returns a properly calculated DesiredSize. So far, everything is working.
However, when having a simple object like this...
public class DataItem
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
...and changing the TextBlock Binding to <TextBlock Text="{Binding Path=Value1}"... the resulting view is identical, however the Measure(...) method doesn't return the expected values, the height of the Items is always zero. Also not working: keep Text Binding and override DataItems ToString() method. View working, Measure doesn't.
I was then trying to force a recalculation using methods like InvalidateMeasure() or UpdateLayout() on the DataTemplate or the whole page, without success.
Can you explain this?
UPDATE
Interesting: I've attached a simple custom ValueConverter to the TextBlock's Binding just for debugging reasons. When a string object is bound, I can see that Measure(...) is triggering the Binding - it's resolved first (i can see the debugger stepping into the ValueConverter) and measured afterwards. But when binding a custom class as described above, Measure(...) doesn't touch the Binding, i am stepping into the ValueConverters breakpoint "later". (Have to find out, when exactly)
Does this help you in any kind?

The answer is simple.. you working not in the 'silverlight way'
In Silverligth - it dosen't mather if string fits to the screen width or not, if string dosen't fit, just set TextBlock.Wrap to Wrap...
You have problem with this becose of 'old way of thinking'...
But if you want it so much try this:
var ContainerGrid = new Grid(); // create grid at runtime
// !!! it's important for controlToMesure.Parent property to be NULL, if it's not
// !!! then temporary remove controlToMesure from parent container...
ContainerGrid.Children.Add(controlToMesure); // add control that you want to mesure
ContainerGrid.Measure(new Size(pageWidth, pageHeight));
ContainerGrid.Arrange(new Rect(0, 0, pageWidth, pageHeight));
ContainerGrid.UpdateLayout();
var size = ((FrameworkElement)ContainerGrid.Children[0]).DesiredSize;

Here is the code from http://silverpdf.codeplex.com/
Maybee it would help you, but you have to modify it, to make it usable.
private System.Windows.Size CalculeteSize()
{
var s = new System.Windows.Controls.StackPanel()
{
VerticalAlignment = System.Windows.VerticalAlignment.Center,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center
};
var fs = FontPool.GetFontStream(Typeface.FontFamily.Source);
s.Children.Add(new System.Windows.Controls.TextBlock
{
Text = Text,
FontSource = new FontSource(fs),
FontSize = EmSize,
FontFamily = Typeface.FontFamily,
FontStretch = Typeface.FontStretch,
FontStyle = Typeface.FontStyle,
FontWeight = Typeface.FontWeight,
});
s.Measure(new System.Windows.Size(double.MaxValue, double.MaxValue));
var aw = s.DesiredSize.Width;
var ah = s.DesiredSize.Height;
var size = new System.Windows.Size(aw, ah);
return size;
}

Solved
the problem was that the page controls were created and calculated first, and added to the displaying control after generation, because i wanted to avoid frequent UI updates. Something similar was even suggested by Ai_boy, who was trying to solve the problem by using an independent Grid Control - unfortunately this turned out as a misleading approach. Only after the generated page control was added to the visual tree, it automatically resolves the Bindings resulting in a proper size measuring.
Hope this helps anyone.

Related

When does a ComboBox receive its Items if it is bound to ObservableCollection?

I am attempting a save/load mechanism for re-use in a business application. I have the groundwork laid to read/write ObservableCollection<> to/from xml, using attributes to describe my class properties. That part is working. I can save an ObservableCollection to XML, then load the XML back into an ObservableCollection the next time I run the program.
Here's my problem. I have a ComboBox whose ItemsSource.DataContext = ObservableCollection<Flag>;
When I run the program, it accepts the binding just fine, but the ComboBox itself does not populate itself until later. I want to set the SelectedItem to be the first item in the ObservableCollection<Flag> that I have loaded from XML. Nothing happens though, because as the program is executing it's startup methods, the Items.Count remains 0. I'm guessing the ComboBox doesn't populate itself until it gets focus. How do I work around this? Can I force the ComboBox to populate itself? I've tried cb_ARDAR_ARFlag.Items.Refresh();
XAML:
<ComboBox Name="cb_ARDAR_ARFlag"
ItemsSource="{Binding}"
SelectionChanged="cb_ARDAR_ARFlag_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Flag_Desc}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Relevant Code:
public MainWindow()
{
InitializeComponent();
setDataBinding();
loadSavedData();
}
private void setDataBinding()
{
//Returns ObservableCollection<Flag>
cb_ARDAR_ARFlag.DataContext = Flag.getOCAvailableFlags();
}
private void loadSavedData()
{
//When it gets here the ItemCount is 0 so nothing happens.
//Refresh didn't help
cb_ARDAR_ARFlag.Items.Refresh();
Flag f = Enforcement_Save.loadOCARFlag().First();
cb_ARDAR_ARFlag.SelectedItem = f;
}
At this point I'm still not sure the code at the end will successfully identify the correct 'flag' item to be selected, or if I'll end up using Linq. Which, by the way, leads me to another question. Can you Linq to ComboBox.Items somehow?
I have recreated your issue, and your are correct, the items count is = 0 in the loadSavedData method. The combobox doesn't seem to be populated until after the constructor has fully executed.
In the meantime I found you can use the ItemsSource property to load the combobox at the time you want it loaded:
cb_ARDAR_ARFlag.ItemsSource = Flag.getOCAvailableFlags();

TreeView incorrectly shows items as expanded when using VirtualizationMode.Recycling

Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.

Why is <Image Source='...'> so slow, and what can I do about it?

Considering the following sample XAML file, which shows the first 1000 people of Facebook, starting with markz as 4th person. Note that this is only a sample. Any Window with 1000 element, no matter how you construct it, is a good demonstration.
<Window x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
And the code behind:
public partial class MainWindow : Window
{
public MainWindow() {
InitializeComponent();
string[] urls = new string[1000];
for (int i = 0; i < 1000; ++i) {
urls[i] = "http://graph.facebook.com/" + i + "/picture";
}
this.DataContext = urls;
}
}
On a very reasonable desktop and high speed connection, the program is extremely slow. Trying to scroll with the ScrollBar ... say to the middle, will take 30 seconds. Hitting the 'Home' and 'End' keys will take significant amount of time.
This isn't a first-time-only-get-images-to-the-cache issue. Going back and forth and seeing pictures already presented is somewhat faster but generally very slow. It appears that nothing is stored in cache, closing the application and restarting it, everything is slow again.
The equivalent HTML code is bleezing fast. Some slowness first time, but then everything is very fast.
What is going on? Does element use any caching at all? Does the list do any pre-fetch of the images not currently presented? Is there anyway to tell it to do? Is it really that my only solution is to manage Bitmap objects myself, along with caching and prefetching logic? If so, any previous work I can incorporate?
EDIT (summary):
#H.B. answer to turn off virtualization will give you best result. The entire listbox is rendered as soon as the Window load, and no image is re-computed
#Phil code works great, and it improves performance, especially when going back and forth.
Without any additional code, WPF will not cache the images between invocation. The WinINET cache is NOT used. Although the request comes with Cache instruction in the HTTP Header, WPF does nothing with it.
ListBoxes virtualize the items by default, so if you scroll down the items are created on the fly. At first it needs to download the image, then it decodes it. If you have scrolled through all the images they may be cached but the ListBox will still recreate the Image controls and hence the images need to be decoded again every time.
You could turn off virtualization by setting the VirtualizingStackPanel.IsVirtualizing attached property to false on the ListBox then everything will be loaded right away, or you can change the VirtualizationMode to Recycling, then the Images (and containing ListBoxItems) won't be thrown away once created.
An alternative would be to add your own image caching so that images are only download once.
Using my example you would put this in your constructor
this.DataContext = new ViewModel();
The following class would store the url and then download the image when the Image property was first accessed.
public class CachingImage
{
private readonly Uri _uri;
public CachingImage(string uriString)
{
_uri = new Uri(uriString, UriKind.RelativeOrAbsolute);
}
private BitmapImage _image;
public ImageSource Image
{
get
{
if (_image == null)
{
_image = new BitmapImage(_uri);
_image.DownloadCompleted += (sender, args) => ((BitmapImage)sender).Freeze();
}
return _image;
}
}
}
Here's the view model
public class ViewModel
{
public ViewModel()
{
Images = Enumerable.Range(1, 1000).Select(i => new CachingImage("http://graph.facebook.com/" + i + "/picture"));
}
public IEnumerable<CachingImage> Images { get; private set; }
...
and of course you would need to change your xaml slightly
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

In C# (or in C# with WPF), how would I build a checkbox at run time?

In C# (or in C# with WPF), how would I build a checkbox at run time?
I would I be able to query the check box to see if the user clicked on it?
In other words, suppose I have a "grid" on which I want to have displayed some checkboxes. But I do not know how many checkboxes to display. I suppose I could (in WPF) fill the grid with checkboxes at design time and mark them as hidden (or visibly == false) and then show them at run time. But I was hoping there was a more elegant way to do this.
There are several ways to do this in WPF. A quick and dirty approach would be to do something like this:
<StackPanel x:Name="CheckBoxes" />
Then in your code behind do:
for (int i=0; i < 10; i++) {
this.CheckBoxes.Children.Add(new CheckBox());
}
But while at first glance it looks simple, this makes it somewhat of a pain to work with in the long run. Instead, a better solution would be to have a class that has a boolean property such as:
// this should really implement INotifyPropertyChanged but
// we'll ignore that for now...
public class SelectableThing {
public bool IsSelected {
get;
set;
}
public string Description {
get;
set;
}
}
Then in your XAML, you would have a bindable control such as ItemsControl:
<ItemsControl x:Name="CheckBoxes">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
Content="{Binding Description}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then in your code behind you could create a collection of these SelectableThing's and set them as the ItemsSource.
private SelectableThing[] things;
// where you do this is up to you really
private void Window_Load(object sender, RoutedEventArgs e) {
things = new SelectableThing[] {
new SelectableThing("First Thing"),
new SelectableThing("Second Thing"),
new SelectableThing("Third Thing")
};
CheckBoxes.ItemsSource = things;
}
In an event handler or something like that, eventually a method that gets called, you could do this. Let's say your Canvas is called myCanvas.
var cb = new CheckBox { //... set the properties, e.g.:
Checked = true, Content = "Check me" };
// do whatever you like to do with your newly created CheckBox
myCanvas.Children.Add(cb);
Hope this helps; of course you can do this inside a loop. If you need to hold a specific set of references to the created CheckBoxes be aware of that or use the Tag Property to identify these special CheckBoxes. Also, you could check myCanvas.Children for CheckBoxes.

WPF Debugging AvalonEdit binding to Document property

all day long I am sitting and trying to find out why binding to AvalonEdits Document property isn't working. AvalonEdit is an advanced WPF text editor - part of the SharpDevelop project.(it's going to be used in SharpDevelop v4 Mirador).
So when I set up a simple project - one TextEditor (that's the AvalonEdits real name in the library) and made a simple class that has one property - Document and it returns a dummy object with some static text the binding is working perfectly.
However in real life solution I'm binding a collection of SomeEditor objects to TabControl.
TabControl has DataTemplate for SomeEditor and there's the TextEditor object.
<TabControl Grid.Column="1" x:Name="tabControlFiles" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type m:SomeEditor}">
<a:TextEditor
Document="{Binding Path=Document, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NoopConverter}, IsAsync=True}"
x:Name="avalonEdit"></a:TextEditor>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style BasedOn="{StaticResource TabItemStyle}" TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
This doesn't work. What I've investigated so far:
DataContext of TextEditor is set to the proper instance of SomeEditor
TextEditors Document property is set to some other instance than SomeEditor.Document property
when I set breakpoint to no-op converter that is attached to that binding it shows me the correct value for Document (the converter is used!)
I also dug through the VisualTree to obtain reference to TextEditor and called GetBindingExpression(TextEditor.DocumentProperty) and this did return nothing
WPF produces the following information:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Document; DataItem='SomeEditor' (HashCode=26280264); target element is 'TextEditor' (Name='avalonEdit'); target property is 'Document' (type 'TextDocument')
SomeEditor instance that is bound to already has a created and cached copy of Document before the binding occurs. The getter is never called.
Anyone can tell me what might be wrong? Why BindingExpression isn't set ? Why property getter is never called?
//edit: new tests and new results
I've read some more and set the binding in code behind. When I do that it works.
How come setting this in XAML doesn't work and doing the same thing in code does?
//edit2: The code also fails when called immediately after adding the object to the observable collection that is used as higher level DataSource.(that's not long after the xaml binding should fire). That makes me think this is timing issue. Anyone can tell something about it ?
//edit3: The binding code:
private List<T> GetObjectOfTypeInVisualTree<T>(DependencyObject dpob) where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(dpob);
List<T> returnlist = new List<T>();
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dpob, i);
T childAsT = child as T;
if (childAsT != null)
{
returnlist.Add(childAsT);
}
List<T> lst = GetObjectOfTypeInVisualTree<T>(child);
if (lst != null)
{
returnlist.AddRange(lst);
}
}
if (returnlist.Count > 0)
{
return returnlist;
}
return null;
}
private void RebindMenuItem_Click(object sender, RoutedEventArgs e)
{
foreach (XHTMLStudioPrototypeFileEditor ed in CurrentProject.OpenedFiles)
{
List<ContentPresenter> cps = GetObjectOfTypeInVisualTree<ContentPresenter>(tabControlFiles);
if (cps != null)
{
foreach (ContentPresenter cp in cps)
{
foreach (DataTemplate dt in tabControlFiles.Resources.Values)
{
try
{
object o = dt.FindName("avalonEdit", cp);
TextEditor ted = (TextEditor)o;
bool isDataBound = BindingOperations.IsDataBound(ted, TextEditor.DocumentProperty);
if (!isDataBound)
{
BindingOperations.SetBinding(ted, TextEditor.DocumentProperty, new Binding("Document"));
}
Console.WriteLine(isDataBound);
}
catch (Exception)
{
}
}
}
}
}
}
Here are six more things to try:
Search your carefully application for any place at all where you directly assign to the Document property of a TextEditor. It looks like some code, somewhere is doing an avalonEdit.Document = ... which would overwrite the binding. I would search your entire app for the match-case whole-word strings "Document" and "DocumentProperty" and give each occurence a moment's thought to see if it could be setting this property.
Set a breakpoint in TextEditor.OnDocumentChanged to see if the document is being properly bound and then changed back later. Check call stacks with "Just My Code" disabled and showing external code.
Try setting breakpoints in the NoopConverter.Convert, SomeEditor.get_Document, and TextEditor.OnDocumentChanged to figure out the precise sequence of operations. Also note when the Binding error message is shown.
Temporarily modify TextEditor's constructor to store a reference to every instance in a public static List field so you can determine which TextEditors have ever been created, then write code that looks through them displaying their GetHashCode() and their BindingOperations.GetBindingExpression(editor, DocumentProperty) results. Make sure you take out the public static field when you're done!
Take the "Path=" out of your XAML that constructs the Binding so it will better match the C# version. (I once had a problem where the XAML interpreted the path different than the Binding constructor because of the ITypeDescriptorContext passed to PropertyConverter.) The exact equivalent to the C# code you posted is Document="{Binding Document}".
Create a custom trace listener and set a breakpoint in it to get the call stack when the binding error is produced, search up the stack frames to find the objects involved and give them debugger object ids (right-click, Make Object ID), then investigate the actual values of properties to make sure they are as expected.
Enjoy!
Just an observation: I had the same problem and looked through the AvalonEdit source; it seems the problem is that the TextEditor constructor overwrites the Document property (instantiates a new TextDocument); if you comment this out, the bindings work; however, if you don't have a binding, you'd need to make further modifications. I'll try to discuss this with the authors and maybe suggest a patch.

Resources