WPF Temp PlaceHolder For 3rd Party Control loading to long - wpf

I have a UI taking a long time because of a intensive 3rd Party Control on this View.
So I am trying to solve it by having a Temp Grid when a property of IsStillLoading = true
When IsStillLoading = false the grid gets replaced by the 3rd Party Control
I do this by using DataTemplates
<Style x:Key="ViewLoadingHelper" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDataLoading}" Value="true">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid x:Name="DummyBeforeLoad" Background="LightGray" >
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsDataLoading}" Value="false">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<myThirdPartyControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Grid>
<ContentControl Style="{StaticResource ViewLoadingHelper}"/>
</Grid>
I have 2 questions:
1) Is this the correct approach? Or would you do it differently?
2) Where can I get or what is this blurry placeholder called.
I am currently using a gray grid, but for example when you look at Facebook, hit the refresh on your browser. For a brieve moment you will see a list of dummy/placeholders posts before seeing the real posts. This is the idea I have and whant to implement this.

Related

Dynamically set image of each node

So I have 3 levels of nodes in my TreeView:
A single Root Node (should display Image1.png)
Some second level nodes (should display Image2.png)
Each second level node has some third-level nodes (should display Image3.png)
I'm trying to use a DataTemplate to dynamically assign the display image to each node, depending upon its level. Since level is not as easily available in WPF as it is in WinForms, I simply resorted to using Tag property of TreeViewItems to store their level. Then I wrote this following Style for assigning display images:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="img" Width="20" Height="20" Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag}" Value="0">
<Setter Property="Source" Value="Icons\Image1.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag}" Value="1">
<Setter Property="Source" Value="Icons\Image2.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock VerticalAlignment="Center" Text="{Binding}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Yes, you guessed it; it doesn't work. Can someone please identify where the problem lies? Or am I doing it just the wrong way?
Right now, your {Binding Tag} will try to find the Tag property of the TreeViewItem's DataContext, not the DependencyProperty. And since, i'm guessing, there is no Tag property in the DataContext, it won't work. If you look at your output Windows in VS, you should see binding errors all over the place.
What you need to do is add the relative source to your binding so it looks at the TreeViewItem instead of the DataContext. Here is an example :
<DataTrigger Binding="{Binding Path=Tag, RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}}" Value="0">

WPF ControlTemplate Height

I have the following style for validating input in my controls:
<Style x:Key="MyErrorTemplate" TargetType="Control">
<Style.Setters>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="ControlErrorTemplate">
<StackPanel Orientation="Vertical" Height="Auto">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder x:Name="Holder"/>
</StackPanel>
<Label Foreground="Red" Content="{Binding ElementName=Holder,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
If an error happens, the error message in the label appears under the control (e.g. textbox) and overlaps the control below. I made StackPanel's Height="Auto", but it didn't help. Each control is in a Grid cell, and the Grid's row Height is also Auto.
Could you please tell me what I am missing? I want the error message to push what is below down.
Thanks.
Validation.ErrorTemplate shows error feedback on an adorner layer. This means all controls in this template will not be considered when the layout system is measuring and arranging the controls on the adorned element layer.
I found this and thanks LPL, i did not know that about the adorner layer.
My solution was a margin "hack". I just used the trigger:
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Margin" Value="0,0,0,28"/>
</Trigger>
</Style.Triggers>
To increase the bottom margin of the adorned textbox. I set the margin large enough to make room for a single string textblock/label and then the content below was moved down

Change ListView.ItemTemplate on subelement change

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

Dynamically Display Grid Content

I have 3 different layouts (similar to I guess what you would call Skins but the layouts are hugely different, not just changes to colors and fonts) which I have developed for my application. The layouts are used for displaying the same data, but in a completely different format. Each of these layouts have been constructed within their own Grid.
I want my application to decide which layout to display dynamically based on a string value available at runtime.
What's the best way to get a parent Grid to display a Child Grid dynamically?
I'm trying to find some sort of magical DataTemplate / DataBinding / Templating method but just can't seem to find the best way. Alternatively, should I be looking at a different method of displaying these different layouts? Like an ItemsControl or similar?
Ben
I usually use a ContentControl and DataTrigger to determine what ContentTemplate to use.
For example,
<ContentControl Content="{Binding MyViewModel}">
<ContentControl.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="DefaultTemplate" />
</DataTemplate>
<DataTemplate x:Key="TemplateA">
<TextBlock Text="Template A" />
</DataTemplate>
<DataTemplate x:Key="TemplateB">
<TextBlock Text="Template B" />
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedView}" Value="ViewA">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedView}" Value="ViewB">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

WPF ComboBox that shows nothing selected when disabled (IsEnabled == false)

I'm thinking out different ways to have a WPF ComboBox show blank as if nothing is selected when IsEnabled is set to false. Like always I'm trying to do this without having to redefine the whole control template for the ComboBox which is always a struggle I have with WPF. If anybody has any solutions more elegant than redefining the whole ComboBox control template please let me know.
The reason for what I'm trying to do is I have a CheckBox that represents an "All" option and when checked it disables the ComboBox which is used to pick only a single individual item. If my CheckBox is checked it is sometimes confusing to the users to see a value remaining in the ComboBox since that value has no meaning in that state of the UI.
Another requirement is that the solution cannot modify the SelectedValue, SelectedIndex, or SelectedItem values of the ComboBox since I would like to retain the previuosly selected item in the case that the users unchecks the "All" CheckBox.
Solution based on HCL's answer:
<ComboBox IsEnabled="{Binding ElementName=myCheckBox, Path=IsChecked}"
ItemsSource="{Binding Path=MyItems}"
SelectedValue="{Binding Path=MySelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="content" Content="{Binding MyItemDescription}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=IsEnabled}"
Value="False">
<Setter TargetName="content"
Property="Visibility"
Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You can do something with triggers:
Try setting the ItemTemplate to an empty DataTemplate when the box is disabled. This will affect the rendering of the selected item and therefore hide it.
Another simple but not very nice solution would be to set the foreground color to the same as a background color.
I believe you can do this with a Style, rather than redefining the control template. Use a Trigger on the IsEnabled property to set the text shown in the ComboBox. Altering the SelectedItem would be my first approach, but since you don't want to do that, you may find success setting the DisplayMemberPath. Something like this (untested)...
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Trigger.Setters>
<Setter Property="DisplayMemberPath" Value="{x:Null}"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
Here's a style that does what you want. It employs a technique that I use all the time: a grid that contains multiple versions of the control, and data triggers that ensure that only one version is visible at any one time.
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<DockPanel>
<CheckBox x:Name="IsActive" DockPanel.Dock="Left"/>
<Grid>
<ComboBox
ItemsSource="{TemplateBinding ItemsSource}"
SelectedItem="{TemplateBinding SelectedItem}"
SelectedIndex="{TemplateBinding SelectedIndex}"
SelectedValue="{TemplateBinding SelectedValue}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<ComboBox>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
This preserves the selected item, selected index, and selected value, just as you want. In fact, it does this a little too well; there's not actually a way of telling that the user deactivated the combo box, since there's no property on ComboBox that exposes this information. I'd probably actually implement this as a custom control derived from ComboBox that exposed the value of the check box as an IsActive property. There are lots of other ways to do it.

Resources