WPF C# Show images in stack panel - wpf

I am actually solving the issue with stack panel and images I would like to show in it.
What my approach was:
Because of just about 50 pictures to be shown , I just get ti image , make a thumbnail from it , and place that one by one to the stackpanel on left side of my program. (As bitmap)
User is able to click on image and do the action with a image.
Required amount was about 50 images.
New state:
New state is that the required amount of images is about 500 so 10 times more.
The problem is even my thumbnail is too small when I am adding it like :
foreach image in list do :
create thumbnail
add on click or and on touch event to that thumbnail
add that thumbnail to the stack panel
I saw somewhere was used a picture box , not sure if that will help me.
I am thinking about creating lists of pictures links (50 in every ) and put for instance first in a scroll bar and when scrollbar reaches the bottom, load next and when the top reaches load previous list.
The problem is I am reaching with ~ 175 images the maximum of process memory.
I am waiting for garbage collector to do its job after every cycle.
Maybe my approach is not good or should be different so this is why I am asking which approach use in order to solve this problem you suggest.
The source of the photos is simply folder of thumbnails of size 150x150 .
The images are added one by one in certain period of time ( one foto every one second or so )
Thank you

An example of implementation.
Create a collection of images sources.
The collection can contain a string or an Url with a path to the image.
In this case, the image will be created only when it is shown in the Window.
You can also set an instance of ImageSource (or rather classes derived from it).
In this case, the image will be immediately loaded into memory.
Also set the property for specifying the selected image.
using System;
using System.Collections.ObjectModel;
using System.Windows.Media.Imaging;
namespace ImagesViewer
{
public class ImagesViewerViewModel
{
public ObservableCollection<object> ImagesSource { get; }
= new ObservableCollection<object>()
{
"https://miro.medium.com/max/2400/0*UEtwA2ask7vQYW06.png",
new Uri("https://149351115.v2.pressablecdn.com/wp-content/uploads/2021/06/stackoverflow-prosus-blue-orange.png"),
new BitmapImage(new Uri("https://amazinghiring.com/wp-content/uploads/2019/03/jpddcgb89ow.jpg"))
};
public object SelectedImage { get; set; }
}
}
Bind the ListBox source to the created collection.
In the Element Template, specify the Image with the binding to the element and the thumbnail sizes you need.
Assign the selected item to the property for the selected image.
The ListBox has a VirtualizingStackPanel built in by default, so you don't need to take any additional steps to limit using memory.
In the viewing region, set the Image with a binding to the property for the selected image.
<Window x:Class="ImagesViewer.ImagesViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ImagesViewer"
mc:Ignorable="d"
Title="ImagesViewerWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:ImagesViewerViewModel/>
</FrameworkElement.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ImagesSource}"
SelectedItem="{Binding SelectedImage}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Margin="5"
Width="100" Height="100"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Image Grid.Column="1"
Source="{Binding SelectedImage}"/>
</Grid>
</Window>
Ok but scoriling of list of 170 150x150 causing the slowinest of the program as I already mentioned . I did the test already. Can you confirm or refute that please ?
Тестирование.
More than 1200 images in JPG and PNG formats are pre-recorded in the folder "C:\150x150".
The ViewModel gets a list of files in this folder and creates a string array with their paths.
The time for this operation is also recorded.
public class ImagesViewModel
{
public IEnumerable<string> ImagesPaths { get; }
public long ExecutionTimeGetPaths {get;}
public ImagesViewModel()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
ImagesPaths = Directory.GetFiles("C:/150x150")
.Where(path => path.EndsWith(".jpg") || path.EndsWith(".png"))
.ToArray();
stopwatch.Stop();
ExecutionTimeGetPaths = stopwatch.ElapsedMilliseconds;
}
}
In View in the upper right corner displays the time taken to get the paths of all files.
On the left side is a ListBox showing images in size 150x150 for all files.
<Window x:Class="Eee.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Eee"
mc:Ignorable="d"
Title="MainWindow" Height="1000" Width="300">
<Window.DataContext>
<local:ImagesViewModel/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding ExecutionTimeGetPaths}"
HorizontalAlignment="Right" VerticalAlignment="Top"
FontSize="30" Margin="10"/>
<ListBox ItemsSource="{Binding ImagesPaths}" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"
Width="150" Height="150"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Results: the time of obtaining the paths is 12-14 ms, the images are displayed instantly.
There are NO LAGS AT ALL when scrolling.
The same was verified for image files of arbitrary format from 128x128 to 4200x2800.
The result is the same.

Related

WPF Caliburn Micro: Exchanging UserControls in a Window dynamically using ContentControl

This question is related to Add a usercontrol to caliburm micro dynamically.
I have read any other related threads before open this new thread, but I still don't understand and find no solution. Please accept my apology if some of you take this as duplicate.
I have a window (MainView) contains "main" Grid (aka LayoutRoot) with 2 columns.
On left column there are 2 buttons: "Display View 1" and "Display View 2".
If user click "Display View 1", the "Display1View" (is a UserControl contains TextBlock with Text "View 1") should be shown on the right column, replace the current one.
If user click "Display View 2", the "Display2View" (is a UserControl contains TextBlock with Text "View 2") should be shown on the right column, replace the current one.
My sample code contains following views and viewmodels:
MainView.xaml and MainViewModel.cs
Display1View.xaml and Display1ViewModel.cs
Display2View.xaml and Display2ViewModel.cs
In my sample code the ContentControl doesn't recognize the UserControl. What am I doing wrong? How to bind ContentControl correctly? Please feel free to modify my sample code. Thank you in advance
MainView.xaml
<Window x:Class="TestCaliMiContentControl.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main View"
Width="525"
Height="350">
<Grid x:Name="LayoutRoot" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30*" />
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftNavPanel" Grid.Column="0">
<Button x:Name="Display1" Content="Display View 1" />
<Button x:Name="Display2" Content="Display View 2" />
</StackPanel>
<ContentControl x:Name="MainGridContent" Grid.Column="1" />
</Grid>
</Window>
MainViewModel.cs
public class MainViewModel : PropertyChangedBase
{
private ContentControl _mainGridContent;
public ContentControl MainGridContent
{
get { return _mainGridContent; }
set
{
_mainGridContent = value;
NotifyOfPropertyChange(() => MainGridContent);
}
}
public void Display1()
{
//MainGridContent = new Display1ViewModel(); // cannot convert source type error
}
public void Display2()
{
// MainGridContent = new Display2ViewModel(); // cannot convert source type error
}
}
Display1View.xaml
<UserControl x:Class="TestCaliMiContentControl.Display1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<TextBlock HorizontalAlignment="Center" FontSize="72"
Text="View 1"/>
</Grid>
</UserControl>
Display1ViewModel.cs
using System;
using System.Windows.Controls;
using Caliburn.Micro;
namespace TestCaliMiContentControl
{
public class Display1ViewModel : PropertyChangedBase {}
}
First, I would start by recommending you read the Caliburn.Micro documentation, specifically the part about Screens, Conductors, and Composition: http://caliburnmicro.com/documentation/composition
That being said, we can modify your code to get it working.
1) Since your MainViewModel is supposed to be conducting other items, it should descend from Conductor<T>. In this case, we will have it conduct the Caliburn Screen class.
public class MainViewModel : Conductor<Screen>
2) In MVVM, you view models should know nothing of your view. You should not see UI classes such as ContentControl. We could change your property to be of type Screen, but we actually don't need that property at all since we are using a conductor. So, remove the MainGridContent property and backing field.
3) Within your Display1 and Display2 methods, invoke Caliburn's conductor method ActivateItem to show the appropriate item.
public void Display1()
{
ActivateItem(new Display1ViewModel());
}
4) In your MainView.xaml you will need to bind your ContentControl to the conductor's active item property, which is, by convention, ActiveItem.
<ContentControl x:Name="ActiveItem" Grid.Column="1" />
5) Finally, since your conductor is conducting Screens, you need to make them screens. Screens are helpful because they have lifecycle and allow you to know when they are activated/deactivated. Do this for both Display1 and Display2.
public class Display1ViewModel : Screen {}
This should get you up and running.

AvalonDock Now Loses Alt Key Adornments

I've been using AvalonDock (2.0) for some time now, being key for managing documents in an IDE. In the last month or so, I've noticed that Alt key adornments are no longer showing up for controls within AvalonDock, though the Alt key commands are executing as expected. See the image below where the Alt key adornments are showing up in the menu, but not for the buttons inside AvalonDock:
What is particularly interesting about this issue, that it appears to be triggered by an environmental setting or condition.
As demonstrated in this video (at 2:07), the alt key adornments are working for a control within AvalonDock. But, if I now use the very same executable I used in that video, the alt key adornments do not work.
I'm currently using build 2.0.1746, but I also tried build 2.0.2000 (with the Xceed namespace) and found the same issue. I also tried the version packaged with the Xceed Extended WPF Toolkit, and found that the issue persists.
I also built a very simple test application which loads a couple of documents of type Item, where Item is a simple class with a Name property:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:avalonDock="clr-namespace:Xceed.Wpf.AvalonDock;assembly=Xceed.Wpf.AvalonDock"
xmlns:avalonDockLayout="clr-namespace:Xceed.Wpf.AvalonDock.Layout;assembly=Xceed.Wpf.AvalonDock"
xmlns:avalonDockControls="clr-namespace:Xceed.Wpf.AvalonDock.Controls;assembly=Xceed.Wpf.AvalonDock"
Title="MainWindow" Height="500" Width="500">
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Item}">
<StackPanel Orientation="Vertical">
<Label Content="Item:" />
<TextBox Text="{Binding Name}"/>
<Button Content="_ClickMe" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<avalonDock:DockingManager Grid.Row="0" DocumentsSource="{Binding}">
<avalonDockLayout:LayoutRoot>
<avalonDockLayout:LayoutPanel Orientation="Horizontal">
<avalonDockLayout:LayoutDocumentPane/>
</avalonDockLayout:LayoutPanel>
</avalonDockLayout:LayoutRoot>
</avalonDock:DockingManager>
<Button Grid.Row="1" Content="_Test" />
</Grid>
</Window>
Even with this simple application, the Alt key adornments show up for the Test button outside of AvalonDock, but not for the ClickMe buttons for the controls within AvalonDock.
I also posted the issue here on the AvalonDock codeplex site, but there appears to be very little response activity. I also posted the issue here on the Extended Toolkit codeplex site.
Any ideas on how to correct or workaround this issue?
It sounds as though this will be fixed with the next version of AvalonDock.
In the meantime, the following Blend behavior is a workaround:
public class FixKeyboardCuesBehavior : Behavior<UIElement>
{
private static readonly DependencyProperty ShowKeyboardCuesProperty;
static FixKeyboardCuesBehavior()
{
Type keyboardNavigation = typeof(KeyboardNavigation);
var field = keyboardNavigation.GetField("ShowKeyboardCuesProperty", BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(field != null, "field != null");
ShowKeyboardCuesProperty = (DependencyProperty)field.GetValue(null);
}
protected override void OnAttached()
{
base.OnAttached();
Window rootWindow = Window.GetWindow(this.AssociatedObject);
if (rootWindow == null)
{
return;
}
BindingOperations.SetBinding(
this.AssociatedObject,
ShowKeyboardCuesProperty,
new Binding("(KeyboardNavigation.ShowKeyboardCues)") { Source = rootWindow });
}
}
Use this from XAML by adding the following to the root element of your DataTemplate for the AvalonDock LayoutItemTemplate:
<i:Interaction.Behaviors
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<my:FixKeyboardCuesBehavior />
</i:Interaction.Behaviors>
This workaround uses internal implementation details of WPF to re-introduce the Alt behaviour below the broken AvalonDock logical tree. So, I'll be looking forward to being able to zap it from my code when AD itself is fixed!

Combobox Dropdown event triggers parent window Z index to change

I have a problem with a WPF business line application prototype that I do not know where to start to debug.
My Master Window (A) opens a "dialog" window using Dialog.Show(). The child window (B) has a combobox on it.
When the combobox is clicked (to expand and to show all the options) the entire second window (B) is hidden except the dropdownlist appearing from the combobox. The combobox isn't there, the window isn't there. Nothing is there except the dropdownlist and the master window behind it. If I click the master window then focus is once again set to window B and it shows as it should (because I set the Owner of window B to be window A).
To make it more interesting this bug is not consistent. It appears maybe 1 of 20 times I use the application, and when it starts appearing it can appear several times in a row and then stop happening again.
Possibly related is the fact that I think I have the same bug some times with MessageBoxes. When using MessageBox.Show() (Win forms msgbox) from a viewmodel when only the master window (A) is showing the box occationally appears behind the master window making it invisible for the user.
My application is using GalaSoft MvvmLight (if that could have anything to do with it) and quite a few telerik components. Other than that I am not sure what data to provide. I don't think anyone can tell me the source of the problem based on this information, but where would you start looking for the problem?
Update :
Good news! I have isolated the problem and found the combination of prerequisites for the phenomena to occur:
a) A component showing a PDF file is currently open anywhere withing the application.
b) A telerik component has been undocked and redocked.
I will include the code below, but I think the issue is buried deep within the RadDocking or WebBrowser component.
MainWindow
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OpenWindowClick(object sender, RoutedEventArgs e)
{
var w = new TestWindow { Owner = this, DataContext = new TestViewModel()};
w.Show();
}
}
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerikDocking="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"
x:Class="TelerikGridDockingVsBrowserVsWindowBug.MainWindow"
Title="MainWindow" Height="750" Width="925">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<telerikDocking:RadDocking Grid.Row="0">
<telerikDocking:RadSplitContainer>
<telerikDocking:RadPaneGroup>
<telerikDocking:RadPane Header="I make bugs combined with a webbrowser showing a pdf document">
<TextBlock Text="1. Drag me and dock on the same or another location"/>
</telerikDocking:RadPane>
</telerikDocking:RadPaneGroup>
</telerikDocking:RadSplitContainer>
</telerikDocking:RadDocking>
<Button Grid.Row="1" Click="OpenWindowClick" Content="2. Open window" Height="50" Margin="0,20"/>
<WebBrowser Grid.Row="2" Source="http://www.kb.nl/sites/default/files/docs/pdf_guidelines.pdf"/>
</Grid>
TestWindow :
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
}
}
<Window x:Class="TelerikGridDockingVsBrowserVsWindowBug.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="150" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock> 3. Click on the combobox.</TextBlock>
<ComboBox Grid.Row="1" ItemsSource="{Binding Options}" SelectedItem="{Binding SelectedOption}" Height="50"></ComboBox>
<TextBlock Grid.Row="2"> This window will then disappear behind its owner</TextBlock>
</Grid>
TestWindow ViewModel:
public class TestViewModel
{
public List<string> Options { get; set; }
public string SelectedOption { get; set; }
public TestViewModel()
{
Options = new List<string> { "String1", "String2" };
SelectedOption = Options.First();
}
}
I have an open ticket with Telerik about this, so I will update here if I find out anything.
I have been in your situation before... you have an unexplained problem... it doesn't make any sense... you've tried everything that you can think of... in a last ditch effort to fix the problem, you throw it out to all the experienced users here... then nothing... no responses... no help.
The thing is that if you can't find and fix the problem with all of your code right in front of you, then how can we fix it without any code?
All I can suggest is for you to try to locate exactly where the problem originates. You can do this by commenting out sections of code and then running your solution to see if that has removed the problem or whether it still exists. The fact that your problem only occurs infrequently will make this stage even more tricky.
However, with perseverance, you should be able to narrow down where the problem arises. Once you have managed to exclude enough of your code, the last step is to see if you re-create it in a much smaller new WPF project. If you manage to get to this stage, please come back and show your reduced code... then, we may be able to help you further.

UI elements obscured from UIAutomation by UserControl

I have an automation client that uses the AutomationElement.FromPoint method to get the AutomationElement under the cursor:
AutomationElement element = AutomationElement.FromPoint(point);
Typically this works great, but I keep running into a problem with certain WPF applications. The problem occurs when there is a UserControl on the same UI level as another important UI element.
For example:
<Window x:Class="wpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:c="clr-namespace:wpfTestApp">
<Window.Resources>
<c:NameList x:Key="NameListData"/>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource NameListData}}"
Height="300" HorizontalAlignment="Left" Margin="10,10,0,0" Name="listBox1"
VerticalAlignment="Top" Width="153" >
</ListBox>
<UserControl Name="exampleUserControl">
<TextBlock Visibility="Hidden">Loading...</TextBlock>
</UserControl>
</Grid>
</Window>
When I attempt to point to any of the listbox items (or even the listbox itself), all I get is the "exampleUserControl".
I know that there are other methods for obtaining AutomationElements that don't depend on location, but in this case that is my only option, since we are trying to get the element under the cursor. The problem is, in this case, the important element (i.e. the listbox items) are covered up by this unimportant item ("exampleUserControl" containing "Loading..." text).
Is there any alternative to the FromPoint method, or some way that I can get it to ignore such elements?
There are several methods can find/search the Element.
You can find these interfaces on the MSDN http://msdn.microsoft.com/en-us/library/ms606809
For instance:
AutomationElement.FindFirst - http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.findfirst
AutomationElement.FindAll - http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.findall
// Sample: Find the first Element and the classname = ""SciCalc"
AutomationElement calcRoot = AutomationElement.RootElement.FindFirst(TreeScope.Children,
new PropertyCondition(AutomationElement.ClassNameProperty, "SciCalc"));
"AutomationElement.RootElement" is the parent of all the currently opened windows and controls.
For improve the performance you can find the target window first and then scan the controls on the AutomationElement of the Target window.
For instance: You can find and create the target WPF window via "AutomationElement.FindFirst" or "AutomationElement.FromHandle" first and search you listbox on the target window.
The clean solution to your problem is to set Visibility="Hidden" to the UserControl instead of the TextBlock

How to update Dynamic Resource within a Dynamic Resource?

I have a visual brush which is a group of shapes, the main colour of which is a dynamic resource itself - so the shape is for example MyShape and the Colour, MyColour which is referenced by the Shape object.
My problem is when I update the colour for this - it only happens the first time the shape is loaded (the colour needs to be set first) however as much as I change the colour it won't update the dynamic resource that uses the colour - how do I make this work?
Just need to make a dynamic resource work within another dynamic resource and have them both update when I change the colour.
I have no idea how to get this to work - I spent time creating a colour-picker for WPF only to find I cannot change the colour of this item - 1-Tier resources work where I set the brush/colour directly but not a colour within another object or 2-Tier Resource.
Edit: My problem seems to be specific to using these in a seperate Resource / Dictionary as my program needs to access this item from a class not the Window, the main example mentioned does not work when the MyColor is in a seperate Resource.
Unless I misunderstand the situation, exactly what you're talking about works pretty well. I just tried it out with this Xaml:
<Window x:Class="ConditionalTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="MyColor" Color="Aqua" />
<VisualBrush x:Key="MyBrush">
<VisualBrush.Visual>
<Ellipse Height="50" Width="100" Fill="{DynamicResource MyColor}" />
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
<Grid Background="{DynamicResource MyBrush}">
<Button Height="30" Width="Auto" VerticalAlignment="Center" HorizontalAlignment="Center" Content="ChangeColor" Click="Button_Click" />
</Grid>
</Window>
And then changed the color in the click handler for that button:
private void Button_Click(object sender, RoutedEventArgs e)
{
((SolidColorBrush)Resources["MyColor"]).Color = Colors.Purple;
}
And it worked like a champ.
Can you post an example of how you are attempting to change the color in the resource dictionary?
When I make a sample app and try to change the resource value it appears that the SolidColorBrush in the resource dictionary has been frozen so it can't be modified. To get around this I just set the new value to a new SolidColorBrush.

Resources