i make gallery in my app
when i back and choose another album system.OutOfMemoryException occurs
this is my Xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ScrollViewer Margin="0,1,0,71" >
<Grid Width="475" Name="picbtn" Background="WhiteSmoke" >
</Grid>
</ScrollViewer>
</Grid>
this is my code
string type;
int x = 0;
int y = 4050;
int i = 0;
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (e.NavigationMode != NavigationMode.Back)
{
type = NavigationContext.QueryString["name"];
ReadFromXml(type);
}
}
private void ReadFromXml(string type)
{
XDocument xml;
if (type == "day")
{
xml = XDocument.Load("DayimagesData.xml");
}
else
{
xml = XDocument.Load("NightmagesData.xml");
}
var query = from c in xml.Root.Descendants("posts")
select c.Element("image").Value;
foreach (var name in query)
{
CreateGrid(name);
i++;
if (i % 3 == 0)
{
x += 150;
y -= 150;
}
}
}
private void CreateGrid(string data)
{
BitmapImage image = new BitmapImage(
new Uri(#"images\" + data, UriKind.Relative)
);
Grid g1 = new Grid
{
Width = 150,
Height = 150,
Margin = new Thickness(0, x, 0, y),
DataContext = data
};
Image im = new Image
{
Source = image
};
g1.Children.Add(im);
if (i % 3 == 0)
{ g1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; }
else if (i % 3 == 1)
{
g1.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
}
else
{
g1.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
}
picbtn.Children.Add(g1);
g1.Tap += Grid_Tap;
}
is there are any method to clear the memory when the back clicked ??
i try
GC.collect
but the same issue happened
Dispose XDocument and BitmapImage properly .From your code those are the memory consumers on high note.
Related
There is a Grid, which is filled dynamically with Image controls in code behind(Sorry for that).
Grid has 1 column, many pages, each page has 1 Border with Image as Border.Child inside. What I need is to Zoom (Scale) my Image in Grid when Button.Click event fires. I used Scale Transform with the Image before, but I didn't manage to bind Grid element Image with the Click handler.
Please suggest, how I can zoom images inside grid, step by step.
Thanks in advance!
Yes, I know this is horrible, should be done in different way, I'm still learning, how to do this right.
Method, that generates Grid. After that ZOOM click method ( only for zoom, there is another method for zoom out)
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
}
}
Here is a very simple version of a scalable ItemsControl in a ScrollViewer.
It might be improved in many ways. First of all, you should replace handling Button Click events by binding the Button Command properties to ZoomIn and ZoomOut commands in the view model (left out for brevity).
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding Scale}"/>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="LightGray">
<Image Source="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content=" + " Click="ZoomInButtonClick"/>
<Button Content=" - " Click="ZoomOutButtonClick"/>
</StackPanel>
</Grid>
The code behind:
public partial class MainWindow : Window
{
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
foreach (string imageFile in Directory.EnumerateFiles(
#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"))
{
viewModel.Images.Add(new BitmapImage(new Uri(imageFile)));
}
}
private void ZoomInButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale *= 1.1;
}
private void ZoomOutButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale /= 1.1;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private double scale = 1;
public double Scale
{
get { return scale; }
set
{
scale = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Scale)));
}
}
}
I managed to find an ugly, horrible solution, sorry for that. Use it only, if there is no any alternative. Please, add answers with better solutions. Thanks for your time!
We need to add (in code behind) Image.LayoutTransform defined as ScaleTransform:
imageListViewItem.LayoutTransform = new ScaleTransform();
I used // to emphasize changes in method below. Also, most changes happened in Zoom/ZoomOut methods below.
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
// Add HERE!!!
imageListViewItem.LayoutTransform = new ScaleTransform();
//
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
We take all elements from the Grid and Scale(Zoom) them, then we clear the Grid.Children and fill it with new Items.
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX - 0.2) < 0.8 || (imgViewerScaleTransform.ScaleY - 0.2) < 0.8)
return;
imgViewerScaleTransform.ScaleX += -0.2;
imgViewerScaleTransform.ScaleY += -0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
I'm using MaterialSkin UI controls , how can I change the control properties to Right to left, because it's by default Left to Right.
I think this is the code should be edited :
private void UpdateTabRects()
{
_tabRects = new List<Rectangle>();
//If there isn't a base tab control, the rects shouldn't be calculated
//If there aren't tab pages in the base tab control, the list should just be empty which has been set already; exit the void
if (_baseTabControl == null || _baseTabControl.TabCount == 0) return;
//Calculate the bounds of each tab header specified in the base tab control
using (var b = new Bitmap(1, 1))
{
using (var g = Graphics.FromImage(b))
{
_tabRects.Add(new Rectangle(SkinManager.FormPadding, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[0].Text, SkinManager.Font_Size11).Width, Height));
for (int i = 1; i < _baseTabControl.TabPages.Count; i++)
{
_tabRects.Add(new Rectangle(_tabRects[i - 1].Right, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[i].Text , SkinManager.Font_Size11).Width , Height));
}
}
}
}
and this is the full code for the control :
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
using MaterialSkin.Animations;
namespace MaterialSkin.Controls
{
public class MaterialTabSelector : Control, IMaterialControl
{
[Browsable(false)]
public int Depth { get; set; }
[Browsable(false)]
public MaterialSkinManager SkinManager => MaterialSkinManager.Instance;
[Browsable(false)]
public MouseState MouseState { get; set; }
private MaterialTabControl _baseTabControl;
public MaterialTabControl BaseTabControl
{
get { return _baseTabControl; }
set
{
_baseTabControl = value;
if (_baseTabControl == null) return;
_previousSelectedTabIndex = _baseTabControl.SelectedIndex;
_baseTabControl.Deselected += (sender, args) =>
{
_previousSelectedTabIndex = _baseTabControl.SelectedIndex;
};
_baseTabControl.SelectedIndexChanged += (sender, args) =>
{
_animationManager.SetProgress(0);
_animationManager.StartNewAnimation(AnimationDirection.In);
};
_baseTabControl.ControlAdded += delegate
{
Invalidate();
};
_baseTabControl.ControlRemoved += delegate
{
Invalidate();
};
}
}
private int _previousSelectedTabIndex;
private Point _animationSource;
private readonly AnimationManager _animationManager;
private List<Rectangle> _tabRects;
private const int TabHeaderPadding = 24;
private const int TabIndicatorHeight = 2;
public MaterialTabSelector()
{
SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer, true);
Height = 48;
_animationManager = new AnimationManager
{
AnimationType = AnimationType.EaseOut,
Increment = 0.04
};
_animationManager.OnAnimationProgress += sender => Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.Clear(SkinManager.ColorScheme.PrimaryColor);
if (_baseTabControl == null) return;
if (!_animationManager.IsAnimating() || _tabRects == null || _tabRects.Count != _baseTabControl.TabCount)
UpdateTabRects();
var animationProgress = _animationManager.GetProgress();
//Click feedback
if (_animationManager.IsAnimating())
{
var rippleBrush = new SolidBrush(Color.FromArgb((int)(51 - (animationProgress * 50)), Color.White));
var rippleSize = (int)(animationProgress * _tabRects[_baseTabControl.SelectedIndex].Width * 1.75);
g.SetClip(_tabRects[_baseTabControl.SelectedIndex]);
g.FillEllipse(rippleBrush, new Rectangle(_animationSource.X - rippleSize / 2, _animationSource.Y - rippleSize / 2, rippleSize, rippleSize));
g.ResetClip();
rippleBrush.Dispose();
}
//Draw tab headers
foreach (TabPage tabPage in _baseTabControl.TabPages)
{
var currentTabIndex = _baseTabControl.TabPages.IndexOf(tabPage);
Brush textBrush = new SolidBrush(Color.FromArgb(CalculateTextAlpha(currentTabIndex, animationProgress), SkinManager.ColorScheme.TextColor));
g.DrawString(tabPage.Text.ToUpper(), SkinManager.Font_Size11, textBrush, _tabRects[currentTabIndex], new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
textBrush.Dispose();
}
//Animate tab indicator
var previousSelectedTabIndexIfHasOne = _previousSelectedTabIndex == -1 ? _baseTabControl.SelectedIndex : _previousSelectedTabIndex;
var previousActiveTabRect = _tabRects[previousSelectedTabIndexIfHasOne];
var activeTabPageRect = _tabRects[_baseTabControl.SelectedIndex];
var y = activeTabPageRect.Bottom - 2;
var x = previousActiveTabRect.X + (int)((activeTabPageRect.X - previousActiveTabRect.X) * animationProgress);
var width = previousActiveTabRect.Width + (int)((activeTabPageRect.Width - previousActiveTabRect.Width) * animationProgress);
g.FillRectangle(SkinManager.ColorScheme.AccentBrush, x, y, width, TabIndicatorHeight);
}
private int CalculateTextAlpha(int tabIndex, double animationProgress)
{
int primaryA = SkinManager.ActionBarText.A;
int secondaryA = SkinManager.ActionBarTextSecondary.A;
if (tabIndex == _baseTabControl.SelectedIndex && !_animationManager.IsAnimating())
{
return primaryA;
}
if (tabIndex != _previousSelectedTabIndex && tabIndex != _baseTabControl.SelectedIndex)
{
return secondaryA;
}
if (tabIndex == _previousSelectedTabIndex)
{
return primaryA - (int)((primaryA - secondaryA) * animationProgress);
}
return secondaryA + (int)((primaryA - secondaryA) * animationProgress);
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (_tabRects == null) UpdateTabRects();
for (var i = 0; i < _tabRects.Count; i++)
{
if (_tabRects[i].Contains(e.Location))
{
_baseTabControl.SelectedIndex = i;
}
}
_animationSource = e.Location;
}
private void UpdateTabRects()
{
_tabRects = new List<Rectangle>();
//If there isn't a base tab control, the rects shouldn't be calculated
//If there aren't tab pages in the base tab control, the list should just be empty which has been set already; exit the void
if (_baseTabControl == null || _baseTabControl.TabCount == 0) return;
//Calculate the bounds of each tab header specified in the base tab control
using (var b = new Bitmap(1, 1))
{
using (var g = Graphics.FromImage(b))
{
_tabRects.Add(new Rectangle(SkinManager.FormPadding, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[0].Text, SkinManager.Font_Size11).Width, Height));
for (int i = 1; i < _baseTabControl.TabPages.Count; i++)
{
_tabRects.Add(new Rectangle(_tabRects[i - 1].Right, 0, TabHeaderPadding * 2 + (int)g.MeasureString(_baseTabControl.TabPages[i].Text , SkinManager.Font_Size11).Width , Height));
}
}
}
}
}
}
If you're using windows forms you would go into the properties of the tab control and make:
RightToLeft = Yes
and
RightToLeftLayout = True.
This is also a duplicate question:
How to make Managed Tab Control (MTC) appear right to left
Im using WinForms. In my Form i have a picturebox and a next button. I use this picturebox to display tiff images and i use the next button to navigate to the next page. The tiff documents are multipage images. The document I'm trying to view has a horizontal images and a vertical images like the example below. If its horizontal i want to size it (1100, 800), but if its vertical i want to size it (800, 1100). How do i do this? currently this is what i have but its not a good solution.
System.Drawing.Image img = System.Drawing.Image.FromFile(path_lbl.Text);
if (img.Height > img.Width)
{
pictureBox1.Width = 800;
pictureBox1.Height = 1300;
}
else
{
pictureBox1.Width = 1300;
pictureBox1.Height = 800;
}
I currently use this approach but this doesn't work because if the first image is vertical the if-statement will always execute the first condition pictureBox1.size(1300 , 800); With this method, if the next image is horizontal the condition will not ever re-size it horizontally.
Example Tiff image
http://www.filedropper.com/verticalandhorizontal
Quick Test Code
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace Demo
{
class TestForm : Form
{
public TestForm()
{
var panel = new Panel { Dock = DockStyle.Top, BorderStyle = BorderStyle.FixedSingle };
openButton = new Button { Text = "Open", Top = 8, Left = 16 };
prevButton = new Button { Text = "Prev", Top = 8, Left = 16 + openButton.Right };
nextButton = new Button { Text = "Next", Top = 8, Left = 16 + prevButton.Right };
path_lbl = new Label { Text = "", Top = 12, Left = 16 + nextButton.Right };
panel.Height = 16 + openButton.Height;
panel.Controls.AddRange(new Control[] { openButton, prevButton, nextButton, path_lbl });
pageViewer = new PictureBox { Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom };
ClientSize = new Size(850, 1100 + panel.Height);
Controls.AddRange(new Control[] { panel, pageViewer });
openButton.Click += OnOpenButtonClick;
prevButton.Click += OnPrevButtonClick;
nextButton.Click += OnNextButtonClick;
Disposed += OnFormDisposed;
UpdatePageInfo();
}
private Button openButton;
private Button prevButton;
private Button nextButton;
private PictureBox pageViewer;
private PageBuffer pageData;
private int currentPage;
private Size pageSize;
public string path;
private Label path_lbl;
private void OnOpenButtonClick(object sender, EventArgs e)
{
using (var dialog = new OpenFileDialog())
{
if (dialog.ShowDialog(this) == DialogResult.OK)
Open(dialog.FileName);
path = dialog.FileName;
}
}
private void OnPrevButtonClick(object sender, EventArgs e)
{
SelectPage(currentPage - 1);
}
private void OnNextButtonClick(object sender, EventArgs e)
{
//var data = PageBuffer.Open(path,Size= new Size(850,1150));
SelectPage(currentPage + 1);
//Debug.WriteLine("Current Size: 1300, 800");
}
private void OnFormDisposed(object sender, EventArgs e)
{
if (pageData != null)
pageData.Dispose();
}
private void Open(string path)
{
var data = PageBuffer.Open(path, new Size(1500, 1500));
pageViewer.Image = null;
if (pageData != null)
pageData.Dispose();
pageData = data;
SelectPage(0);
}
private void SelectPage(int index)
{
pageViewer.Image = pageData.GetPage(index);
currentPage = index;
UpdatePageInfo();
}
private void UpdatePageInfo()
{
prevButton.Enabled = pageData != null && currentPage > 0;
nextButton.Enabled = pageData != null && currentPage < pageData.PageCount - 1;
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TestForm());
}
}
class PageBuffer : IDisposable
{
public const int DefaultCacheSize = 12; //This is how much images it will have in memory
public static PageBuffer Open(string path, Size maxSize, int cacheSize = DefaultCacheSize)
{
return new PageBuffer(File.OpenRead(path), maxSize, cacheSize);
}
private PageBuffer(Stream stream, Size maxSize, int cacheSize)
{
this.stream = stream;
source = Image.FromStream(stream);
pageCount = source.GetFrameCount(FrameDimension.Page);
if (pageCount < 2) return;
pageCache = new Image[Math.Min(pageCount, Math.Max(cacheSize, 5))];
pageSize = source.Size;
if (!maxSize.IsEmpty)
{
float scale = Math.Min((float)maxSize.Width / pageSize.Width, (float)maxSize.Height / pageSize.Height);
pageSize = new Size((int)(pageSize.Width * scale), (int)(pageSize.Height * scale));
}
var worker = new Thread(LoadPages) { IsBackground = true };
worker.Start();
}
private void LoadPages()
{
while (true)
{
lock (syncLock)
{
if (disposed) return;
int index = Array.FindIndex(pageCache, 0, pageCacheSize, p => p == null);
if (index < 0)
Monitor.Wait(syncLock);
else
pageCache[index] = LoadPage(pageCacheStart + index);
}
}
}
private Image LoadPage(int index)
{
source.SelectActiveFrame(FrameDimension.Page, index);
return new Bitmap(source, pageSize);
}
private Stream stream;
private Image source;
private int pageCount;
private Image[] pageCache;
private int pageCacheStart, pageCacheSize;
private object syncLock = new object();
private bool disposed;
private Size pageSize;
public Image Source { get { return source; } }
public int PageCount { get { return pageCount; } }
public Image GetPage(int index)
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
if (PageCount < 2) return Source;
lock (syncLock)
{
AdjustPageCache(index);
int cacheIndex = index - pageCacheStart;
var image = pageCache[cacheIndex];
if (image == null)
image = pageCache[cacheIndex] = LoadPage(index);
return image;
}
}
private void AdjustPageCache(int pageIndex)
{
int start, end;
if ((start = pageIndex - pageCache.Length / 2) <= 0)
end = (start = 0) + pageCache.Length;
else if ((end = start + pageCache.Length) >= PageCount)
start = (end = PageCount) - pageCache.Length;
if (start < pageCacheStart)
{
int shift = pageCacheStart - start;
if (shift >= pageCacheSize)
ClearPageCache(0, pageCacheSize);
else
{
ClearPageCache(pageCacheSize - shift, pageCacheSize);
for (int j = pageCacheSize - 1, i = j - shift; i >= 0; j--, i--)
Exchange(ref pageCache[i], ref pageCache[j]);
}
}
else if (start > pageCacheStart)
{
int shift = start - pageCacheStart;
if (shift >= pageCacheSize)
ClearPageCache(0, pageCacheSize);
else
{
ClearPageCache(0, shift);
for (int j = 0, i = shift; i < pageCacheSize; j++, i++)
Exchange(ref pageCache[i], ref pageCache[j]);
}
}
if (pageCacheStart != start || pageCacheStart + pageCacheSize != end)
{
pageCacheStart = start;
pageCacheSize = end - start;
Monitor.Pulse(syncLock);
}
}
void ClearPageCache(int start, int end)
{
for (int i = start; i < end; i++)
Dispose(ref pageCache[i]);
}
static void Dispose<T>(ref T target) where T : class, IDisposable
{
var value = target;
if (value != null) value.Dispose();
target = null;
}
static void Exchange<T>(ref T a, ref T b) { var c = a; a = b; b = c; }
public void Dispose()
{
if (disposed) return;
lock (syncLock)
{
disposed = true;
if (pageCache != null)
{
ClearPageCache(0, pageCacheSize);
pageCache = null;
}
Dispose(ref source);
Dispose(ref stream);
if (pageCount > 2)
Monitor.Pulse(syncLock);
}
}
}
}
I want to create a data-binded horizontal layout ItemsControl where for each item there would be a Button. When I add new items to the collection the ItemsControl should grow, relative to the Window it is in, until it reaches it's MaxWidth property. Then all buttons should shrink equally to fit inside MaxWidth. Something similar to the tabs of a Chrome browser.
Tabs with space:
Tabs with no empty space:
So far I've gotten to this:
<ItemsControl Name="ButtonsControl" MaxWidth="400">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type dataclasses:TextNote}">
<Button Content="{Binding Title}" MinWidth="80"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When adding items the expansion of the StackPanel and Window are fine, but when MaxWidth is reached the items just start to disappear.
I don't think it is possible to produce that behaviour using any combination of the standard WPF controls, but this custom StackPanel control should do the job:
public class SqueezeStackPanel : Panel
{
private const double Tolerance = 0.001;
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register
("Orientation", typeof (Orientation), typeof (SqueezeStackPanel),
new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure,
OnOrientationChanged));
private readonly Dictionary<UIElement, Size> _childToConstraint = new Dictionary<UIElement, Size>();
private bool _isMeasureDirty;
private bool _isHorizontal = true;
private List<UIElement> _orderedSequence;
private Child[] _children;
static SqueezeStackPanel()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof (SqueezeStackPanel),
new FrameworkPropertyMetadata(typeof (SqueezeStackPanel)));
}
protected override bool HasLogicalOrientation
{
get { return true; }
}
protected override Orientation LogicalOrientation
{
get { return Orientation; }
}
public Orientation Orientation
{
get { return (Orientation) GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = new Size(_isHorizontal ? 0 : finalSize.Width, !_isHorizontal ? 0 : finalSize.Height);
var childrenCount = Children.Count;
var rc = new Rect();
for (var index = 0; index < childrenCount; index++)
{
var child = _orderedSequence[index];
var childVal = _children[index].Val;
if (_isHorizontal)
{
rc.Width = double.IsInfinity(childVal) ? child.DesiredSize.Width : childVal;
rc.Height = Math.Max(finalSize.Height, child.DesiredSize.Height);
size.Width += rc.Width;
size.Height = Math.Max(size.Height, rc.Height);
child.Arrange(rc);
rc.X += rc.Width;
}
else
{
rc.Width = Math.Max(finalSize.Width, child.DesiredSize.Width);
rc.Height = double.IsInfinity(childVal) ? child.DesiredSize.Height : childVal;
size.Width = Math.Max(size.Width, rc.Width);
size.Height += rc.Height;
child.Arrange(rc);
rc.Y += rc.Height;
}
}
return new Size(Math.Max(finalSize.Width, size.Width), Math.Max(finalSize.Height, size.Height));
}
protected override Size MeasureOverride(Size availableSize)
{
for (var i = 0; i < 3; i++)
{
_isMeasureDirty = false;
var childrenDesiredSize = new Size();
var childrenCount = Children.Count;
if (childrenCount == 0)
return childrenDesiredSize;
var childConstraint = GetChildrenConstraint(availableSize);
_children = new Child[childrenCount];
_orderedSequence = Children.Cast<UIElement>().ToList();
for (var index = 0; index < childrenCount; index++)
{
if (_isMeasureDirty)
break;
var child = _orderedSequence[index];
const double minLength = 0.0;
const double maxLength = double.PositiveInfinity;
MeasureChild(child, childConstraint);
if (_isHorizontal)
{
childrenDesiredSize.Width += child.DesiredSize.Width;
_children[index] = new Child(minLength, maxLength, child.DesiredSize.Width);
childrenDesiredSize.Height = Math.Max(childrenDesiredSize.Height, child.DesiredSize.Height);
}
else
{
childrenDesiredSize.Height += child.DesiredSize.Height;
_children[index] = new Child(minLength, maxLength, child.DesiredSize.Height);
childrenDesiredSize.Width = Math.Max(childrenDesiredSize.Width, child.DesiredSize.Width);
}
}
if (_isMeasureDirty)
continue;
var current = _children.Sum(s => s.Val);
var target = GetSizePart(availableSize);
var finalSize = new Size
(Math.Min(availableSize.Width, _isHorizontal ? current : childrenDesiredSize.Width),
Math.Min(availableSize.Height, _isHorizontal ? childrenDesiredSize.Height : current));
if (double.IsInfinity(target))
return finalSize;
RecalcChilds(current, target);
current = 0.0;
for (var index = 0; index < childrenCount; index++)
{
var child = _children[index];
if (IsGreater(current + child.Val, target, Tolerance) &&
IsGreater(target, current, Tolerance))
{
var rest = IsGreater(target, current, Tolerance) ? target - current : 0.0;
if (IsGreater(rest, child.Min, Tolerance))
child.Val = rest;
}
current += child.Val;
}
RemeasureChildren(finalSize);
finalSize = new Size
(Math.Min(availableSize.Width, _isHorizontal ? target : childrenDesiredSize.Width),
Math.Min(availableSize.Height, _isHorizontal ? childrenDesiredSize.Height : target));
if (_isMeasureDirty)
continue;
return finalSize;
}
return new Size();
}
public static double GetHeight(Thickness thickness)
{
return thickness.Top + thickness.Bottom;
}
public static double GetWidth(Thickness thickness)
{
return thickness.Left + thickness.Right;
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
var removedUiElement = visualRemoved as UIElement;
if (removedUiElement != null)
_childToConstraint.Remove(removedUiElement);
}
private Size GetChildrenConstraint(Size availableSize)
{
return new Size
(_isHorizontal ? double.PositiveInfinity : availableSize.Width,
!_isHorizontal ? double.PositiveInfinity : availableSize.Height);
}
private double GetSizePart(Size size)
{
return _isHorizontal ? size.Width : size.Height;
}
private static bool IsGreater(double a, double b, double tolerance)
{
return a - b > tolerance;
}
private void MeasureChild(UIElement child, Size childConstraint)
{
Size lastConstraint;
if ((child.IsMeasureValid && _childToConstraint.TryGetValue(child, out lastConstraint) &&
lastConstraint.Equals(childConstraint))) return;
child.Measure(childConstraint);
_childToConstraint[child] = childConstraint;
}
private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var panel = (SqueezeStackPanel) d;
panel._isHorizontal = panel.Orientation == Orientation.Horizontal;
}
private void RecalcChilds(double current, double target)
{
var shouldShrink = IsGreater(current, target, Tolerance);
if (shouldShrink)
ShrinkChildren(_children, target);
}
private void RemeasureChildren(Size availableSize)
{
var childrenCount = Children.Count;
if (childrenCount == 0)
return;
var childConstraint = GetChildrenConstraint(availableSize);
for (var index = 0; index < childrenCount; index++)
{
var child = _orderedSequence[index];
if (Math.Abs(GetSizePart(child.DesiredSize) - _children[index].Val) > Tolerance)
MeasureChild(child, new Size(_isHorizontal ? _children[index].Val : childConstraint.Width,
!_isHorizontal ? _children[index].Val : childConstraint.Height));
}
}
private static void ShrinkChildren(IEnumerable<Child> children, double target)
{
var sortedChilds = children.OrderBy(v => v.Val).ToList();
var minValidTarget = sortedChilds.Sum(s => s.Min);
if (minValidTarget > target)
{
foreach (var child in sortedChilds)
child.Val = child.Min;
return;
}
do
{
var tmpTarget = target;
for (var iChild = 0; iChild < sortedChilds.Count; iChild++)
{
var child = sortedChilds[iChild];
if (child.Val*(sortedChilds.Count - iChild) >= tmpTarget)
{
var avg = tmpTarget/(sortedChilds.Count - iChild);
var success = true;
for (var jChild = iChild; jChild < sortedChilds.Count; jChild++)
{
var tChild = sortedChilds[jChild];
tChild.Val = Math.Max(tChild.Min, avg);
// Min constraint skip success expand on this iteration
if (Math.Abs(avg - tChild.Val) <= Tolerance) continue;
target -= tChild.Val;
success = false;
sortedChilds.RemoveAt(jChild);
jChild--;
}
if (success)
return;
break;
}
tmpTarget -= child.Val;
}
} while (sortedChilds.Count > 0);
}
private class Child
{
public readonly double Min;
public double Val;
public Child(double min, double max, double val)
{
Min = min;
Val = val;
Val = Math.Max(min, val);
Val = Math.Min(max, Val);
}
}
}
Try using it as your ItemsPanelTemplate:
<ItemsControl Name="ButtonsControl" MaxWidth="400">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:SqueezeStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type dataclasses:TextNote}">
<Button Content="{Binding Title}" MinWidth="80"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I can't be sure based on the code that you have supplied, but I think you will have better layout results by removing your MaxWidth on the ItemsControl.
You can achieve something like this using a UniformGrid with Rows="1". The problem is that you can either have it stretched or not and neither of these options will do exactly what you want:
If it's stretched, then your "tabs" will always fill the whole available width. So, if you only have 1, it will be stretched across the whole width. If you set MaxWidth for the "tab", then if you have 2 they will not be adjacent but floating each in the middle of its column.
If it's left-aligned, then it will be difficult to get any padding/margin in your control, because when it shrinks, the padding will stay, making the actual content invisible.
So basically you need a control that has a "preferred" width:
When it has more space available than this preferred width, it sets itself to the preferred width.
When it has less space, it just takes up all the space it has.
This cannot be achieved using XAML (as far as I can tell), but it's not too difficult to do in code-behind. Let's create a custom control for the "tab" (namespaces omitted):
<ContentControl x:Class="WpfApplication1.UserControl1">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Border BorderBrush="Black" BorderThickness="1" Padding="0,5">
<ContentPresenter HorizontalAlignment="Center" Content="{TemplateBinding Content}"></ContentPresenter>
</Border>
</ControlTemplate>
</ContentControl.Template>
Code behind:
public partial class UserControl1 : ContentControl
{
public double DefaultWidth
{
get { return (double)GetValue(DefaultWidthProperty); }
set { SetValue(DefaultWidthProperty, value); }
}
public static readonly DependencyProperty DefaultWidthProperty =
DependencyProperty.Register("DefaultWidth", typeof(double), typeof(UserControl1), new PropertyMetadata(200.0));
public UserControl1()
{
InitializeComponent();
}
protected override Size MeasureOverride(Size constraint)
{
Size baseSize = base.MeasureOverride(constraint);
baseSize.Width = Math.Min(DefaultWidth, constraint.Width);
return baseSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
Size baseBounds = base.ArrangeOverride(arrangeBounds);
baseBounds.Width = Math.Min(DefaultWidth, arrangeBounds.Width);
return baseBounds;
}
}
Then, you can create your ItemsControl, using a UniformGrid as the container:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:UserControl1 Content="{Binding}" Margin="0,0,5,0" DefaultWidth="150"></local:UserControl1>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" HorizontalAlignment="Left"></UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Here's a screenshot of the result with 3 items and many items (don't feel like counting them :)
I want to create GUI application using .NET and I need to implement such kind of scrollable list with custom items. All items should grouped to 3 different groups and to include images, text and favourite star button as shown here:
I would also like to implement filtering using search box, but once I would be able to create such list with customizable items then I hope it would be much easier.
I tried using .NET list view, but I want each cell to look like 1 cell - with out any borders between the image, text, image.
I would extremely appreciate any thoughts about this manner!
The best way would be making control template for either WPF ListView or ListBox control.
For example, ListBox.ItemTemplate allows for custom item look.
If you would like to use 3rd party components, Better ListView allows this using owner drawing (requires subclassing BetterListView):
Here is a setup code for the control:
this.customListView = new CustomListView
{
Dock = DockStyle.Fill,
Parent = this
};
this.customListView.BeginUpdate();
for (int i = 0; i < 6; i++)
{
var item = new BetterListViewItem
{
Image = imageGraph,
Text = String.Format("Item no. {0}", i)
};
this.customListView.Items.Add(item);
}
var group1 = new BetterListViewGroup("First group");
var group2 = new BetterListViewGroup("Second group");
var group3 = new BetterListViewGroup("Third group");
this.customListView.Groups.AddRange(new[] { group1, group2, group3 });
this.customListView.Items[0].Group = group1;
this.customListView.Items[1].Group = group1;
this.customListView.Items[2].Group = group2;
this.customListView.Items[3].Group = group2;
this.customListView.Items[4].Group = group2;
this.customListView.Items[5].Group = group3;
this.customListView.AutoSizeItemsInDetailsView = true;
this.customListView.GroupHeaderBehavior = BetterListViewGroupHeaderBehavior.None;
this.customListView.ShowGroups = true;
this.customListView.LayoutItemsCurrent.ElementOuterPadding = new Size(0, 8);
this.customListView.EndUpdate();
and CustomListView class (implements custom drawing and interaction with star icons):
using BetterListView;
internal sealed class CustomListView : BetterListView
{
private const int IndexUndefined = -1;
private Image imageStarNormal;
private Image imageStarHighlight;
private int lastStarIndex = IndexUndefined;
private int currentStarIndex = IndexUndefined;
public CustomListView()
{
this.imageStarNormal = Image.FromFile("icon-star-normal.png");
this.imageStarHighlight = Image.FromFile("icon-star-highlight.png");
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var item = HitTest(e.Location).ItemDisplay;
if (item == null)
{
return;
}
var bounds = GetItemBounds(item);
if (bounds == null)
{
return;
}
Rectangle boundsStar = GetStarBounds(bounds);
UpdateStarIndex(boundsStar.Contains(e.Location)
? item.Index
: IndexUndefined);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
UpdateStarIndex(IndexUndefined);
}
protected override void OnDrawGroup(BetterListViewDrawGroupEventArgs eventArgs)
{
eventArgs.DrawSeparator = false;
base.OnDrawGroup(eventArgs);
}
protected override void OnDrawItem(BetterListViewDrawItemEventArgs eventArgs)
{
base.OnDrawItem(eventArgs);
Graphics g = eventArgs.Graphics;
BetterListViewItemBounds bounds = eventArgs.ItemBounds;
int imageWidth = this.imageStarNormal.Width;
int imageHeight = this.imageStarNormal.Height;
g.DrawImage(
(this.currentStarIndex == eventArgs.Item.Index) ? this.imageStarHighlight : this.imageStarNormal,
GetStarBounds(bounds),
0, 0, imageWidth, imageHeight,
GraphicsUnit.Pixel);
Rectangle boundsSelection = bounds.BoundsSelection;
g.DrawRectangle(
Pens.Gray,
new Rectangle(boundsSelection.Left, boundsSelection.Top, boundsSelection.Width - 1, boundsSelection.Height - 1));
}
private void UpdateStarIndex(int starIndex)
{
if (starIndex == this.lastStarIndex)
{
return;
}
bool isUpdated = false;
if (this.lastStarIndex != IndexUndefined)
{
Items[this.lastStarIndex].Invalidate();
isUpdated = true;
}
if (starIndex != IndexUndefined)
{
Items[starIndex].Invalidate();
isUpdated = true;
}
this.lastStarIndex = this.currentStarIndex;
this.currentStarIndex = starIndex;
if (isUpdated)
{
RedrawItems();
}
}
private Rectangle GetStarBounds(BetterListViewItemBounds bounds)
{
Rectangle rectInner = bounds.BoundsInner;
int widthImage = this.imageStarNormal.Width;
int heightImage = this.imageStarNormal.Height;
return (new Rectangle(
rectInner.Width - widthImage,
rectInner.Top + ((rectInner.Height - heightImage) >> 1),
widthImage,
heightImage));
}
}