floydpink / cachedimage Goto Github PK
View Code? Open in Web Editor NEWA WPF control that wraps the Image control to enable file-system based caching
Home Page: http://floydpink.github.io/CachedImage/
License: MIT License
A WPF control that wraps the Image control to enable file-system based caching
Home Page: http://floydpink.github.io/CachedImage/
License: MIT License
I've got a .Net Core 3.1 WPF project, the bitmap caching stuff doesn't seem to work, I suspect it's an issue with the .Net Core implementation. I havent investigated further to confirm, but if anyone else runs into it the dedicated caching mode should work.
BitmapImage has a UriCachePolicy property. You can simply set it to RequestCacheLevel.Default to get an IE-like cache policy with all exceptions handled properly.
private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var url = e.NewValue as string;
if (string.IsNullOrEmpty(url))
return;
var cachedImage = (Image) obj;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(url);
bitmapImage.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
bitmapImage.EndInit();
cachedImage.Source = bitmapImage;
}
There are also some issues in your code:
I tried to rewrite FileCache.cs before I discovered the UriCachePolicy property of BitmapImage. This is what I have done and it worked correctly in my scenario where hundreds of CachedImage control request some random image simultaneously.
// A private field to record if a file is being written.
private static readonly Dictionary<string, bool> IsWritingFile = new Dictionary<string, bool>();
public static async Task<MemoryStream> HitAsync(string url)
{
if (!Directory.Exists(AppCacheDirectory))
{
Directory.CreateDirectory(AppCacheDirectory);
}
var uri = new Uri(url);
var fileNameBuilder = new StringBuilder();
using (var sha1 = new SHA1Managed())
{
var canonicalUrl = uri.ToString();
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(canonicalUrl));
fileNameBuilder.Append(BitConverter.ToString(hash).Replace("-", "").ToLower());
if (Path.HasExtension(canonicalUrl))
fileNameBuilder.Append(Path.GetExtension(canonicalUrl));
}
var fileName = fileNameBuilder.ToString();
var localFile = string.Format("{0}\\{1}", AppCacheDirectory, fileName);
var memoryStream = new MemoryStream();
FileStream fileStream = null;
if (!IsWritingFile.ContainsKey(fileName) && File.Exists(localFile))
{
using (fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
{
await fileStream.CopyToAsync(memoryStream);
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
var request = WebRequest.Create(uri);
request.Timeout = 30;
try
{
var response = await request.GetResponseAsync();
var responseStream = response.GetResponseStream();
if (responseStream == null)
return null;
if (!IsWritingFile.ContainsKey(fileName))
{
IsWritingFile[fileName] = true;
fileStream = new FileStream(localFile, FileMode.Create, FileAccess.Write);
}
using (responseStream)
{
var bytebuffer = new byte[100];
int bytesRead;
do
{
bytesRead = await responseStream.ReadAsync(bytebuffer, 0, 100);
if (fileStream != null)
await fileStream.WriteAsync(bytebuffer, 0, bytesRead);
await memoryStream.WriteAsync(bytebuffer, 0, bytesRead);
} while (bytesRead > 0);
if (fileStream != null)
{
await fileStream.FlushAsync();
fileStream.Dispose();
IsWritingFile.Remove(fileName);
}
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
catch (WebException)
{
return null;
}
}
In Image.cs:
private static async void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var url = e.NewValue as string;
if (string.IsNullOrEmpty(url))
return;
var memoryStream = await FileCache.HitAsync(url);
if (memoryStream == null)
return;
try
{
var cachedImage = (Image) obj;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
cachedImage.Source = bitmapImage;
}
catch (Exception)
{
// ignored, in case the downloaded file is a broken or not an image.
}
}
I was using the cachedImage:Image
element inside the DataTemplate
of a ListView
inside a UserControl
. The direct builds, both in Debug & Release flavors, were working just fine - but when I generated the setup file using Inno-setup & installed that in other machines, the page started crashing with the following log:
Set property 'System.Windows.Controls.ItemsControl.ItemTemplate' threw an exception.
Stack Trace: at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at MyApp.MyWindow.InitializeComponent() in E:\w_Development\DotNet\MyApp\MyWindow.xaml:line 1
at MyApp.MyWindow..ctor(Window window) in E:\w_Development\DotNet\MyApp\MyWindow.xaml.cs:line 54
at MyApp.MainWindow.<NavigateWithDelay>d__3.MoveNext() in E:\w_Development\DotNet\MyApp\MainWindow.xaml.cs:line 35
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_0(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at MyApp.App.Main()
The DataTemplate
is like the following:
<DataTemplate x:Key="MyItemTemplate">
<Grid Margin="8 4 8 4" Background="#7aBa23">
<Grid Margin="2" Background="White">
<!--<Grid.Effect>
<DropShadowEffect BlurRadius="5" ShadowDepth="2"/>
</Grid.Effect>-->
<Grid.RowDefinitions>
<RowDefinition Height="{Binding MY_TILE_H}" MaxHeight="240"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding MY_TILE_W1}" MaxWidth="256"/>
<ColumnDefinition Width="{Binding MY_TILE_W2}" MaxWidth="124"/>
</Grid.ColumnDefinitions>
<!--<Image Grid.Row="0" Grid.ColumnSpan="2"
Width="{Binding MY_TILE_W}" MaxWidth="380"
Height="{Binding MY_TILE_H}" MaxHeight="240"
Stretch="UniformToFill">
<Image.Source>
<BitmapImage UriCachePolicy="Revalidate"
UriSource="{Binding ImageUrl}"
CreateOptions="IgnoreImageCache"
CacheOption="OnLoad"/>
</Image.Source>
</Image>-->
<cachedImage:Image Grid.Row="0" Grid.ColumnSpan="2"
Width="{Binding MY_TILE_W}" MaxWidth="380"
Height="{Binding MY_TILE_H}" MaxHeight="240"
Stretch="UniformToFill"
ImageUrl="{Binding ImageUrl}"/>
... ... ...
</Grid>
</Grid>
</DataTemplate>
The custom UserControl
, used under a TabControl
in the MyWindow.xaml
has the following skeleton:
<materialDesign:DialogHost x:Name="DialogProgress">
<materialDesign:DialogHost.DialogContent>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
Height="64" Width="Auto" x:Name="StpProgContent"
Orientation="Horizontal" Background="#7ABA23">
<ProgressBar Width="24" Height="24" Margin="16"
Foreground="White"
Style="{DynamicResource MaterialDesignCircularProgressBar}"
IsIndeterminate="True"
Value="33"/>
<TextBlock x:Name="ProgMsg" Text="Posting your order ..."
Padding="12 8 12 8" VerticalAlignment="Center"
Foreground="White" FontWeight="DemiBold" FontSize="18"/>
</StackPanel>
</materialDesign:DialogHost.DialogContent>
<Grid Background="#FaFaFa">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="512"/>
</Grid.ColumnDefinitions>
... ... ...
<ListView x:Name="MyListView" Grid.Column="0"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemTemplate="{DynamicResource MyItemTemplate}"
Grid.RowSpan="2" Margin="0 60 0 0"
SelectionChanged="MyListView_SelectionChanged">
<!--PreviewMouseDown="MyListView_MouseDown"-->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
... ... ...
</Grid>
</materialDesign:DialogHost>
I really like the result this library is delivering in the direct builds, but is there any kind of clash with InnoSetup or any other view in release-mode with the above codes that it's crashing?
I am not an expert in WPF or .Net - but any kind of help can allow me to explore & solve this issue in the right way.
Btw, thanks for the library.
This seems like a really useful tool, but I am trying to figure out how to make the image cache expire after a day.
Hello.
I have in Visual Studio 2019 the issue of an UriFormatException.
I created a new UserControl as a View. Moved to a Namespace "Views".
But I don´t have Anything placed in the UserControl yet.
No ImageCache References or a ImageCache-Control.
<UserControl x:Class="Views.SettingsView"
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:viewModels="clr-namespace:SerienNaGer_Desktop.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid />
</UserControl>
So I get Squiggly Lines under "<UserControl ", and following Exception.
UriFormatException: Ungültiger URI: Das URI-Format konnte nicht bestimmt werden.
UriFormatException: Invalid URI: The URI-Format could not be determined. <-Manual translation
StackTrace:
bei System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
bei System.Uri..ctor(String uriString)
bei CachedImage.Image.<ImageUrlPropertyChanged>d__0.MoveNext()
--- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
bei System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_0(Object state)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
I had a Workaround for the other ImageCache-Controls, where I can´t use the XAML-Designer except I Remove the ImageUrl-Property from the Control temporarily.
This is somehow strange to me.
How does the ImageCache can "Bleed" into a UserControl?
thx in advance Zui
It would be handy to have an event that the image is ready.
CachedImage/source/FileCache.cs
Line 77 in 51cac51
When using the dedicated file cache it times out too quickly and so never loads the image. Should probably be 3000 milliseconds instead of 30.
When using the default Image control like this:
<Image Source="{Binding SelectedSeries.FanArt, IsAsync=True}"/>
the image is loaded on a different thread and the UI does not freeze.
However, when using the CachedImage version:
<c:Image ImageUrl="{Binding SelectedSeries.FanArt, IsAsync=True}"/>
the UI freezes while loading the image.
Am I missing something here or is this an issue?
The WinRT implementation of Image is a sealed class so the initial subclassing will not work in that setting.
Is it able to add a feature to support load a thumbnail of a image before the real image is downloaded
FileCache.AppCacheMode = FileCache.CacheMode.Dedicated;
FileCache.AppCacheDirectory = DirectoryHelper.GetAppRootDirectory(".Caches/Thumbnails/");
AppCacheDirectory == "D:\\Documents\\Visual Studio 2022\\Projects\\XX\\XX\\bin\\Debug\\net6.0-windows\\.Caches\\Thumbnails\\"
WinINet
mode work fine.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.