How can I create an adaptive layout in WPF? - c#

A website can be designed to adapt to smaller screen sizes using media queries. For example, three columns on a wide screen, one column on a low-resolution phone.
Is there a similar technique for WPF to adjust the layout based on available screen space or parent control size?
For example, I'd like to have 3 columns displayed horizontally on a large screen, but displayed vertically on smaller screen. Ideally, I'd like to formulate layouts like this: "If this control's width is less than 400 points, rearrange these controls in that way."
How can I create an adaptive design like this in WPF? That is, define different layouts for controls for specific parent control sizes?
Ideally controls should be rearranged instead of duplicated or recreated, to avoid being extremely slow.

The easiest way to do this is with DataTriggers and a Converter to test if the bound value is greater or less than a parameter.
This will allow you to easily adjust the style setters based on a bound value. For example, you could use
<Style x:Key="MyControlStyle">
<!-- Default Values -->
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="0" />
<Style.Triggers>
<DataTrigger Value="True"
Binding="{Binding ActualHeight, ElementName=MyWindow,
Converter={StaticResource IsValueLessThanParameter},
ConverterParameter=400}">
<!-- Values to use when Trigger condition is met -->
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
In the case where you have a more complex layout with many pieces that change based on some triggered value, you can replace entire Templates with your trigger instead of just individual properties
<Style x:Key="MyContentControlStyle" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource BigTemplate}" />
<Style.Triggers>
<DataTrigger Value="True"
Binding="{Binding ActualHeight, ElementName=MyWindow,
Converter={StaticResource IsValueLessThanParameter},
ConverterParameter=400}">
<Setter Property="ContentTemplate" Value="{StaticResource LittleTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
I believe you can also bind to the SystemParameters object to use additional information about the application in your bindings, although I can't remember the exact syntax for it right now.

If you're using the UWP flavour of WPF, then you might use AdaptiveTrigger:
<AdaptiveTrigger MinWindowWidth="720" MinWindowHeight="900" />

The only way I know to do something like this is in code, and you'll need to create a custom layout. The simplest way of doing that is to create a new class that inherits from Panel, and implement MeasureOverride and ArrangeOverride. I've done custom layouts before, and they can end up being a rather large pain to get working for all cases. If you Google "wpf custom layout" you'll get some good examples to start with. Given all the functionality you want, you'll definitely have your work cut out for you. You'll probably want to look at attached properties to see about putting annotations in the xaml to give your code an idea of what should be included at the different sizes.

Related

UWP listview mutiple ItemContainerStyle

How can I have my ListView have multiple ItemContainerStyle?
<ListView x:Name="SongsListView">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<!--<Style TargetType="ListViewItemPresenter">
<Setter Property="SelectedPointerOverBackground" Value="White" />
</Style>-->
</ListView.ItemContainerStyle>
</ListView>
The way you're trying to add multiple styles doesn't make any sense. You can't have more than one Style applied on a control at the same time. There is no point in doing that. However you can ask UWP to apply one of the many Styles programmatically.
UWP, just like WPF, supports ItemContainerStyleSelector. This is a simple class that allows you to select a Style based on some condition or procedure. You may check one working example here.

WPF: Style.Setter Changes ComboBox Colors, Won't Change Back

I've got a ComboBox I'm hiding and showing with a Style.Setter on the Visibility property:
<ComboBox ItemsSource="{Binding ElementName=BVTWindow, Path=DataContext.AreaList}" SelectedItem="{Binding Path=Area}">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Setters>
<Setter Property="Visibility" Value="Collapsed" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=BVTWindow, Path=DataContext.IdentitySelection}" Value="Test Management">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
This works great. Unfortunately, applying the style setter (for some reason) removes the color theme from the ComboBox. Now it's a light grey box with white text and nearly unreadable.
I tried adding Foreground and Background attributes to the ComboBox tag, but it had no effect.
I also tried adding Setter Properties for Foreground and Background to the DataTrigger, also with no effect.
I need to either stop the theme from being removed from the box, manually set the text color, or manually set the background color.
I read one article saying that Windows 8 (which is what I'm using) interferes with ComboBox styling, but there wasn't an easily-understandable solution for it.
Any advice would help me out a lot. Thanks in advance.
This articles explains it pretty well:
http://social.technet.microsoft.com/wiki/contents/articles/24240.changing-the-background-color-of-a-combobox-in-wpf-on-windows-8.aspx
Basically, Windows 8 has a different setup for ComboBox, and it breaks some of the style attributes. You can still style it, but you've got to right click the ComboBox and pick "edit template". This will generate a massive amount of xaml in your project view (color schemes for every possible combination of states), but you probably don't need all of it. I played with commenting / uncommenting sections to figure out what each Trigger and Setter affected, then set my colors and killed the extra parts.
I have a new solution that's much cleaner, faster, easier and better-looking than my previous idea:
Just wrap the ComboBox in a WrapPanel or StackPanel and apply the Style Setter to the parent. The control will retain it's proper style and the parent panel should be transparent anyway, so styles won't matter there. Worst case, you've got a WrapPanel with only one item in it and two extra lines of code in the xaml. This will work with other controls as well, as I just had to do it with a Checkbox.
Here's an example:
<WrapPanel>
<WrapPanel.Style>
<Style TargetType="{x:Type WrapPanel}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.TesterIdentitySelection.CanEdit, ElementName=BVTWindow}" Value="true">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</WrapPanel.Style>
<ComboBox ItemsSource="{Binding Path=DataContext.AreaList, ElementName=BVTWindow}" DisplayMemberPath="Name" SelectedItem="{Binding Path=Area, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ToolTip="Select the testing area."/>
</WrapPanel>
The combo box is inside the wrap panel, and the wrap panel is what has the show/hide behavior applied to it. Since the wrap panel doesn't really have any style itself (just an invisible holder), everything ends up looking great.

creating standard behaviour custom maximise/restore button for custom window in WPF

I have used the example at http://www.youtube.com/watch?v=EuhhL_NF-B0 and downloaded the source code to form the basis of my custom window chrome. i have changed the look a fair bit because it's fairly ugly looking. Additionally I have added custom images for the close, maximise and minimise buttons.
However, I note that standard behaviour is for a different image to be displayed for maximise/restore based on whether the window is in a maximised or normal state at the time.
has anyone got a suggestion for how to do this?
Just add both buttons and hide unnecessary with triggers. Two buttons is convenient to easy implement different behavior.
Add something like this to your template:
<ControlTemplate TargetType="{x:Type Window}">
...
<cc:ImageButton ImageSource=".../CloseWindow.png" x:Name="closeButton" Click="OnCloseClick" />
<cc:ImageButton ImageSource=".../MaximizeWindow.png" x:Name="maximizeButton" Visibility="Collapsed" Click="OnMaximizeClick" />
<cc:ImageButton ImageSource=".../RestoreWindow.png" x:Name="restoreButton" Visibility="Collapsed" Click="OnRestoreClick" />
<cc:ImageButton ImageSource=".../Help.png" x:Name="helpButton" Click="OnHelpClick" />
...
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ResizeMode" Value="CanResizeWithGrip" />
<Condition Property="WindowState" Value="Normal" />
</MultiTrigger.Conditions>
<Setter TargetName="maximizeButton" Property="Visibility" Value="Visible" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Never mind cc:ImageButton - just replace it with Image or any.
Also You may omit the first trigger condition if You do not need to style not resizable windows.
It is also possible to change ImageSource for a single Image. But two buttons are more flexible.
If it is needed i can post full source of my window style. It works fine.

WPF charting, how to change label position? [duplicate]

How do you rotate the text in the axis from horizontal to vertical?
I cant do it through xaml because I am a creating multiple series on the fly and I do not know up front how many I will have until after the control populates.
I need to display the dates vertical or at a slant and not horz.
Thank you again.
This post explains how to rotate the labels in a manner that works for both WPF and Silverlight
http://blogs.msdn.com/b/delay/archive/2010/03/06/turn-your-head-and-check-out-this-post-how-to-easily-rotate-the-axis-labels-of-a-silverlight-wpf-toolkit-chart.aspx
becomes
Not sure if you have found the answer for this, but on the off-chance that you haven't you can try the following:
This is in XAML, but should work no matter how many series' you create as it is styling the axis.
<chart:Chart.Axes>
<chart:DateTimeAxis Orientation="X" ShowGridLines="True">
<chart:DateTimeAxis.AxisLabelStyle>
<Style TargetType="{x:Type chart:AxisLabel}">
<Setter Property="StringFormat" Value="{}{0:d-MMM}" />
<Setter Property="RenderTransformOrigin" Value="1,0.5" />
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="-45" />
</Setter.Value>
</Setter>
</Style>
</chart:DateTimeAxis.AxisLabelStyle>
</chart:DateTimeAxis>
<chart:LinearAxis ShowGridLines="True" />
</chart:Chart.Axes>
Maybe, that will help:
http://community.devexpress.com/blogs/bryan/archive/2011/01/19/silverlight-and-wpf-charts-changing-the-axis-label-angles.aspx
I know, it's in xaml, but i don't think there is another way, wpf charting is by far not so comfortable like windows forms charting (where you can easily rotate the labels via property).
For your needs you might to write the style into a resource and reference it in your code-behind.

WPF DataGrid DataContext extremely slow

I have a simple WPF application I'm working on, which performs a SQL query and displays the resulting data in a DataGrid.
Everything works as expected, except that performance is terrible. The length of time from clicking a button to load data, and actually seeing the data show up in the DataGrid, is on the order of 3-4 seconds. It is a good bit faster with row virtualization turned on, but I've had to turn it off since I need to be able to perform operations on cells that are no longer visible after scrolling. And even with virtualization turned on, getting the data displayed is slower than I would like.
I first assumed it was the SQL database that was being slow, but I did some tests and found that I'm reading all data from the SQL server (several hundred rows) into a DataTable in a fraction of a second. It isn't until I bind the DataTable to the DataGrid's DataContext that everything locks up for several seconds.
So why is the DataContext so slow? My computer is brand new, so I have a hard time understanding why it takes any length of time to fill out the DataGrid, considering how quickly I retrieve the data in the first place.
(I also tried binding to the DataGrid's ItemSource rather than the DataContext, but performance was the same.)
Is there an alternative method of loading data into a DataGrid that has more reasonable performance? Even an alternative to DataGrid might be worth exploring if needed.
Edit: At Vlad's suggestion, I tried another test which bypassed the SQL query. I instead filled the DataTable with 1000 rows of random generated data. No change. The data was generated and written to the DataTable in under a second. However, attaching that to the DataGrid took over 20 seconds.
Below is the DataGrid XAML I'm using. Other than the binding, it is very simple, no custom code attached to it.
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="dataGridWellReadings" GridLinesVisibility="None" CanUserResizeRows="False" SelectionUnit="Cell" AlternatingRowBackground="#FFE0E0E0" RowBackground="#FFF0F0F0" HorizontalScrollBarVisibility="Disabled" SelectedCellsChanged="dataGridWellReadings_SelectedCellsChanged" EnableRowVirtualization="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Date" Binding="{Binding readingDate, StringFormat=yyyy-MM-dd}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Pt" Binding="{Binding readingPt, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Pc" Binding="{Binding readingPc, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Ppl" Binding="{Binding readingPpl, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="MCFD" Binding="{Binding readingMCFD, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Water Produced" Binding="{Binding readingWaterProduced, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Water Hauled" Binding="{Binding readingWaterHauled, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Temperature" Binding="{Binding readingTemperature, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Hours On (actual)" Binding="{Binding readingHoursOnActual, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Hours On (planned)" Binding="{Binding readingHoursOnPlanned, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Clock Cycles" Binding="{Binding readingClockCycles, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
There are too many variables to answer this with certainty. However, here are some things for you to consider:
Is the amount of data you are feeding the grid necessary? Are you possibly giving it too much data than the user will really use? This can slow things down.
Are you rendering the columns or cells with too many templates? This makes your presentation flexible, I know, but too many templates (or controls) can slow things down.
Do you have a lot of value converters in your datagrid? Is it required that every row or every column run some slightly expensive code in order to render?
Is it possible that you have a lot of nested styles (using BasedOn) and, perhaps more important, many triggers in those styles which steal render time to apply?
Are you using a lot of user controls, esp nested controls, in your presentation that might be causing the render delay in your presentation?
Is the binding string you are using for your cells complex? Applying many StringFormat or ElementName or Ancestory lookups? These contribute to slowness in rendering.
Is it possible that a visual brush is being used to show more of the data than is immediately visible to the user? This would short circuit the virtualization logic.
Have you considered using a FallBackValue to your bindings and setting IsAsync to true? Setting this to tru will show FallBackValue until the data is ready.
Are you using many MultiBindings or PriorityBindings in your UI that might be causing your rendering to slow down as it processes more than one field or value?
Are the styles you are using complex? Especially gradient brushes, rendering these can be costly, especially if you are doing it every single row in your grid.
Have you considered using paging so that you have less data? In the end, Nairou, this is the best solution for these types of problems if you can make users accept it.
Are you looking at Memory and CPU usage? Is it possible that the hardware you are using is simply struggling to render the UI you have created here?
Are you watching the debug output to see if there are binding errors or other swallowed errors that contribute to the degrade in performance?
Did you smile at the screen and give your code a good feeling so it is willing to try harder for you? Just kidding, but there are lots of variables - huh?
Have you implemented Commands whose CanExecute handlers are called very frequently and are perhaps expensive to execute? These can be silent killers of performance.
Again, there is no 100% answer to this problem. But these might help.
One more thing to consider is that your enumeration can be an observable list - this will let you load data in parts. If you want to load the first page and in an async process append the next page and the next and so on, the user experience should be very close to loading it all at once except it will be a faster initial render. This can be complex, but it another option for you. Observable lists are nifty like that.
Something like this:
ObservableCollection<User> Users { get; set; }
void LoadUsers()
{
int _Size = 2;
int _Page = 0;
using (System.ComponentModel.BackgroundWorker _Worker
= new System.ComponentModel.BackgroundWorker())
{
_Worker.WorkerReportsProgress = true;
_Worker.DoWork += (s, arg) =>
{
List<User> _Data = null;
while (_Data == null || _Data.Any())
{
_Data = GetData(_Size, _Page++);
_Worker.ReportProgress(_Page, _Data);
}
};
_Worker.ProgressChanged += (s, e) =>
{
List<User> _Data = null;
_Data = e.UserState as List<User>;
_Data.ForEach(x => Users.Add(x));
};
_Worker.RunWorkerAsync();
}
}
List<User> GetData(int size, int page)
{
// never return null
return m_Context.Users.Take(size).Skip(page).ToList();
}
Here's what I want you to take away - binding in WPF is never instant. You will never have a complex form render and bind without SOME delay. You can control the pain here with some of the techniques above. However, you can never remove it all. However, binding in WPF is the most powerful and awesome binding tech. I have ever experienced.
Best of luck!
You might find that the slow performance is not related to the attaching itself, but to the redrawing of the DataGrid that happens when the data is displayed. The delay you mentioned seems fairly excessive.
I had a problem with the DataGrid in which it took literally seconds to refresh after a window resize, column sort, etc. and locked up the window UI while it was doing so (1000 rows, 5 columns).
It came down to an issue (bug?) with the WPF sizing calculations. I had it in a grid with the RowDefinition Height="Auto" which was causing the rendering system to try and recalculate the size of the DataGrid at runtime by measuring the size of each and every column and row, presumably by filling the whole grid (as I understand it). It is supposed to handle this intelligently somehow but in this case it was not.
A quick check to see if this is a related problem is to set the Height and Width properties of the DataGrid to a fixed size for the duration of the test, and try running again. If your performance is restored, a permanent fix may be among these options:
Change the sizes of the containing elements to be relative (*) or
fixed values
Set MaxHeight and MaxWidth of the DataGrid to a fixed value larger
than it could get in normal use
Try another container type with different resizing strategy (Grid,
DockPanel, etc). In fact, the simplest solution I found was to put the datagrid inside a Grid as its immediate container, with the DataGrid as the only element
This might be useful to some:
I had a datagrid that was slow to bind just like the original poster.
The application queried the database and did a lot of logic in a short while and then took one or more seconds to simply bind the observable collection to the datagrid.
In my case it turned out that although most of the data was ready, some of it was lazy-loaded (meaning it did not get loaded until needed - this is a common and useful part of most ORM tools like NHibernate, iBatis etc.). It wasn't needed until the binding happened.
In my case it was not all the data but only one single column that was lazy-loaded.
It turned out that WPF already has a very simple mechanism for handling something like this.
Setting the binding for this column to the following solved the problem:
<Binding Path="SomeProperty" IsAsync="True" FallbackValue="..." />
My datagrid loaded almost instantly. One column contained just the text "..." for a few seconds, and then the correct data appeared.
For lack of seeing any of your code, I'd suggest installing a free trial of a performance or memory profiler (eg http://www.red-gate.com/products/dotnet-development/). It'll likely very quickly tell you where the bottleneck is.
Are you experiencing this slowness in Debug or Release builds of your application? With or without Visual Studio attached?
If it is in a Debug build with Visual Studio attached, then it could be a DataBinding errors being written to the Output window. I say this, as earlier this evening I resolved a significant pause/slowness in a ListBox that was displaying 5000+ items which was being caused by the default template for ListBoxItem trying to perform a binding for VerticalContentAlignment and HorizontalContentAlignment that always failed.
I am new to WPF but I have found that if you assign EnableRowVirtualization in more than one location on the same data grid, it will nearly cripple your app when rendering the grid. I have found this 2 times on the app we are working on. 1 time time it was set in a style 2 times and the other time it was set in a behavior and a style. I would check to make sure your not virtualizing more than 1 time on the same data grid.

Categories