I would like to use a Window to display one (or optionally two) image(s) in a grid row.
This image could be quite big, so I set the Stretch property to "UniformToFill" and embed the grid into a scrollviewer.
My Image is of app. 800 x 400 px and if I try to load it into my window, it is not displayed in complete width (the horiz. scrollbar stops before the end of the image).
I would like the image(s) to fill the available window area, but to be able to scroll to see it completely. What is wrong?
Thanks for your help!
tabina
This is my code:
The .xaml:
<Window x:Class="Wpf.Dialogs.ImageBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ImageBox" Topmost="True" WindowStartupLocation="CenterOwner" Width="800" Height="600">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" CanContentScroll="True">
<Grid x:Name="gridImages">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" x:Name="img1" Stretch="UniformToFill"/>
<Image Grid.Row="1" x:Name="img2" Stretch="UniformToFill"/>
</Grid>
</ScrollViewer>
</Window>
The code behind:
using System;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
namespace Wpf.Dialogs
{
public partial class ImageBox : Window
{
public ImageBox() : this("Image", string.Empty, 800, 600)
{
}
public ImageBox(string title, string image, int width, int height)
: this(title, new string[] { image }, width, height)
{
}
public ImageBox(string title, string[] images, int width, int height)
{
InitializeComponent();
this.Title = title;
this.Image = images;
}
public string[] Image
{
set
{
if (value != null)
{
var bim = CreateBitmap(value[0]);
this.img1.Source = bim;
if (value.Length == 2)
{
var bi = CreateBitmap(value[1]);
if (bi != null)
{
this.img2.Source = bi;
}
}
else
{
this.img2.Source = null;
}
}
}
}
private BitmapImage CreateBitmap(string file)
{
if (File.Exists(file))
{
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = new Uri(file);
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.EndInit();
return bmp;
}
else
{
return null;
}
}
}
}
You can't use ScrollViewer and at the same time with Stretch="UniformToFill". Use Grid and Stretch="UniformToFill" or ScrollViewer and Grid.
Related
I working on a simple imageviewer app. I control the Stretch property on the binding based on ViewModel property.
The problem occurs when I change the Stretch attribute based on a 'Combobox', bound to ViewModel, and the image 'cuts off' the corners of a wide image when using 'UniformToFill'. Hence to use of a ScrollViewer to be able to scroll the image content.
The problem is the ScrollViewer doesn't seem to show up scrollbars for me to be able to scroll.
WPF Markup:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Other Grids removed -->
<Grid Name="Container" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch}" Name="PhotoImage" />
</ScrollViewer>
</Grid>
I understand if I set a fixed Height and Width to ScrollViewer and Image, it will work. But I want to do it Dynamically:
The ScrollView Will have Height and Width from Parent 'Grid(Contaioner)' Control.
The Image will have Height and Width from itself, but take Stretch to account in that calculation.
Possible to solve with ActualHeight, ActualWidth? And a DependecyProperty?
This is almost impossible, Or I should say it doesn't make a lot of sense to expect ScrollViewer to know the boundaries of an image with Stretch = UniformToFill. According to MSDN:
UniformToFill:
The content (your Image) is resized to fill the destination dimensions (window or grid) while it
preserves its native aspect ratio. If the aspect ratio of the
destination rectangle differs from the source, the source content is
clipped to fit in the destination dimensions (Therefore the image will be cutted off).
So I think what we really need here is to use Uniform + Proper Scaling instead of UniformToFill.
The solution is when Stretch is set to UniformToFill it must set to Uniform and then Image.Width = image actual width * scalingParam and Image.Height= image actual height * scalingParam, where scalingParam = Grid.Width (or Height) / image actual width (or Height). This way ScrollViewer boundaries will be the same as the image scaled size.
I've provided a working solution to give you an Idea, I'm not sure how suitable would it be for your case but here it is:
First I defined a simple view-model for my Images:
public class ImageViewModel: INotifyPropertyChanged
{
// implementation of INotifyPropertyChanged ...
private BitmapFrame _bitmapFrame;
public ImageViewModel(string path, Stretch stretch)
{
// determining the actual size of the image.
_bitmapFrame = BitmapFrame.Create(new Uri(path), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
Width = _bitmapFrame.PixelWidth;
Height = _bitmapFrame.PixelHeight;
Scale = 1;
Stretch = stretch;
}
public int Width { get; set; }
public int Height { get; set; }
double _scale;
public double Scale
{
get
{
return _scale;
}
set
{
_scale = value;
OnPropertyChanged("Scale");
}
}
Stretch _stretch;
public Stretch Stretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
OnPropertyChanged("Stretch");
}
}
}
In the above code BitmapFrame is used to determine the actual size of the image.
Then I did some initializations in my Mainwindow (or main view-model):
// currently displaying image
ImageViewModel _imageVm;
public ImageViewModel ImageVM
{
get
{
return _imageVm;
}
set
{
_imageVm = value;
OnPropertyChanged("ImageVM");
}
}
// currently selected stretch type
Stretch _stretch;
public Stretch CurrentStretch
{
get
{
return _stretch;
}
set
{
_stretch = value;
//ImageVM should be notified to refresh UI bindings
ImageVM.Stretch = _stretch;
OnPropertyChanged("ImageVM");
OnPropertyChanged("CurrentStretch");
}
}
// a list of Stretch types
public List<Stretch> StretchList { get; set; }
public string ImagePath { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
// sample image path
ImagePath = #"C:\Users\...\YourFile.png";
StretchList = new List<Stretch>();
StretchList.Add( Stretch.None);
StretchList.Add( Stretch.Fill);
StretchList.Add( Stretch.Uniform);
StretchList.Add( Stretch.UniformToFill);
ImageVM = new ImageViewModel(ImagePath, Stretch.None);
CurrentStretch = StretchList[0];
}
My Xaml looks like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.Resources>
<local:MultiConverter x:Key="multiC"/>
</Grid.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Image Source="{Binding ImagePath}" Name="PhotoImage">
<Image.Stretch>
<MultiBinding Converter="{StaticResource multiC}">
<Binding Path="ImageVM" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth"/>
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight"/>
</MultiBinding>
</Image.Stretch>
<Image.LayoutTransform>
<ScaleTransform ScaleX="{Binding ImageVM.Scale}" ScaleY="{Binding ImageVM.Scale}"
CenterX="0.5" CenterY="0.5" />
</Image.LayoutTransform>
</Image>
</ScrollViewer>
</Grid>
<ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding StretchList}" SelectedItem="{Binding CurrentStretch}" DisplayMemberPath="."/>
</Grid>
As you can see, I've used a multi-value converter that takes 3 arguments: current image view-model and window width and height. This arguments were used to calculate current size of the area that image fills. Also I've used ScaleTransform to scale that area to the calculated size. This is the code for multi-value converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ImageViewModel)
{
var imageVm = (ImageViewModel)values[0];
// if user selects UniformToFill
if (imageVm.Stretch == Stretch.UniformToFill)
{
var windowWidth = (double)values[1];
var windowHeight = (double)values[2];
var scaleX = windowWidth / (double)imageVm.Width;
var scaleY = windowHeight / (double)imageVm.Height;
// since it's "uniform" Max(scaleX, scaleY) is used for scaling in both horizontal and vertical directions
imageVm.Scale = Math.Max(scaleX, scaleY);
// "UniformToFill" is actually "Uniform + Proper Scaling"
return Stretch.Uniform;
}
// if user selects other stretch types
// remove scaling
imageVm.Scale = 1;
return imageVm.Stretch;
}
return Binding.DoNothing;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
So ultimately i took a discussion with some co-workers and we agreed that we need to fix the problem before a fix. In other words replace Stretch attribute combined with scrollviewer with something more robust that will support extent ability.
The solution I came up with will work for now, and a better solution to the whole problem will be preformed next scrum sprint.
Solution
A custom dependencyproperty that will control width and height depending on stretch attribute currently present on element.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Column="2" Grid.Row="0" Grid.RowSpan="2">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image Name="PhotoImage"
Source="{Binding SelectedPhoto.Value.Image}"
Stretch="{Binding ImageStretch, NotifyOnTargetUpdated=True}}"
extensions:ImageExtensions.ChangeWidthHeightDynamically="True"/>
</ScrollViewer>
</Grid>
Dependency Property
public static bool GetChangeWidthHeightDynamically(DependencyObject obj)
{
return (bool)obj.GetValue(ChangeWidthHeightDynamicallyProperty);
}
public static void SetChangeWidthHeightDynamically(DependencyObject obj, bool value)
{
obj.SetValue(ChangeWidthHeightDynamicallyProperty, value);
}
public static readonly DependencyProperty ChangeWidthHeightDynamicallyProperty =
DependencyProperty.RegisterAttached("ChangeWidthHeightDynamically", typeof(bool), typeof(ImageExtensions), new PropertyMetadata(false, OnChangeWidthHeightDynamically));
private static void OnChangeWidthHeightDynamically(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var image = d as Image;
if (image == null)
return;
image.SizeChanged += Image_SizeChanged;
image.TargetUpdated += Updated;
}
private static void Updated(object sender, DataTransferEventArgs e)
{
//Reset Width and Height attribute to Auto when Target updates
Image image = sender as Image;
if (image == null)
return;
image.Width = double.NaN;
image.Height = double.NaN;
}
private static void Image_SizeChanged(object sender, SizeChangedEventArgs e)
{
var image = sender as Image;
if (image == null)
return;
image.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
if (Math.Abs(image.ActualHeight) <= 0 || Math.Abs(image.ActualWidth) <= 0)
return;
switch (image.Stretch)
{
case Stretch.Uniform:
{
image.Width = Double.NaN;
image.Height = Double.NaN;
break;
}
case Stretch.None:
{
image.Width = image.RenderSize.Width;
image.Height = image.RenderSize.Height;
break;
}
case Stretch.UniformToFill:
{
image.Width = image.ActualWidth;
image.Height = image.ActualHeight;
break;
}
default:
{
image.Width = double.NaN;
image.Height = double.NaN;
break;
}
}
}
The problem may come from the rest of your layout - If the Grid is contained in an infinitely resizable container (a Grid Column/Row set to Auto, a StackPanel, another ScrollViewer...), it will grow with the Image. And so will do the ScrollViewer, instead of activating the scroll bars.
I'm trying to figure out how to constrain a pan so that the image remains entirely within the bounds of it's containing border. Any help this regard would be greatly appreciated. Thanks....
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MapTest.Window1"
Title="PanTest"
Width="484"
Height="400"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="8,8,8,0"
TextWrapping="Wrap">
To pan the image, simply click on it then drag. I'd like to
constrain the pan so that the image remains entirely within
the border, but without resorting to ClipToBounds. Any help
in this regard would be greatly appreciated. Thanks....</TextBlock>
<Border x:Name="border"
Margin="8"
BorderBrush="Black"
BorderThickness="1"
Grid.Row="1">
<Image x:Name="image"
Source="Penguins.jpg"
Opacity="1"
Width="300"
Height="300"
Stretch="Uniform"
RenderTransformOrigin="0.5,0.5" />
</Border>
</Grid>
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace MapTest
{
public partial class Window1 : Window
{
private Point origin;
private Point start;
public Window1()
{
InitializeComponent();
var group = new TransformGroup();
group.Children.Add(new TranslateTransform());
image.RenderTransform = group;
image.MouseLeftButtonDown += image_MouseLeftButtonDown;
image.MouseMove += image_MouseMove;
image.MouseLeftButtonUp += image_MouseLeftButtonUp;
}
private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
image.ReleaseMouseCapture();
}
private void image_MouseMove(object sender, MouseEventArgs e)
{
if (!image.IsMouseCaptured)
return;
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).
Children.First(tr => tr is TranslateTransform);
var vector = start - e.GetPosition(border);
tt.X = origin.X - vector.X;
tt.Y = origin.Y - vector.Y;
}
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
image.CaptureMouse();
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).
Children.First(tr => tr is TranslateTransform);
start = e.GetPosition(border);
origin = new Point(tt.X, tt.Y);
}
}
}
The following code gives you the bounds of the transformed Image control in coordinates relative to the Border control. You could easily check if the bounds are located inside the Border control.
var rect = new Rect(image.RenderSize);
var bounds = image.TransformToAncestor(border).TransformBounds(rect);
I am trying to get some pictures out of PicSet1.dll into a WPF image control. I am not sure how to put the bitmap into the control. Don't see any methods for this in the image class.
System.Reflection.AssemblyName aName;
aName = System.Reflection.AssemblyName.GetAssemblyName("c:\\PicSet1.dll");
asm = System.Reflection.Assembly.Load(aName);
var result = asm.GetManifestResourceNames();
var bitmap= new System.Drawing.Bitmap(asm.GetManifestResourceStream(res[0]));
image1 = bitmap.
You can bind the Image in XAML to an ImageSource once you load the image from your DLL.
The key is to make sure your images in the reference DLL have a Build Action = Embedded Resource.
Here is the XAML:
<Window x:Class="LoadImage.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="{Binding EmbeddedPicture}"/>
</Grid>
</Window>
Here is the ViewModel:
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace LoadImage.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
System.Reflection.AssemblyName aName;
aName = System.Reflection.AssemblyName.GetAssemblyName("PicSet1.dll");
if (aName != null)
{
Assembly asm = System.Reflection.Assembly.Load(aName);
if ( asm != null )
{
string[] resources = asm.GetManifestResourceNames();
if ((resources != null) && (resources.Length > 0))
{
string name = resources[0];
if (!string.IsNullOrEmpty(name))
{
using (Stream myImage = asm.GetManifestResourceStream(name))
{
if (myImage != null)
{
using (System.Drawing.Image photo = System.Drawing.Image.FromStream((Stream) myImage))
{
MemoryStream finalStream = new MemoryStream();
photo.Save(finalStream, ImageFormat.Png);
// translate to image source
PngBitmapDecoder decoder = new PngBitmapDecoder(finalStream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
EmbeddedPicture = decoder.Frames[0];
}
}
}
}
}
}
}
}
private ImageSource _embeddedPicture;
public ImageSource EmbeddedPicture
{
get
{
return _embeddedPicture;
}
set
{
_embeddedPicture = value;
OnPropertyChanged("EmbeddedPicture");
}
}
}
}
I have a dataform with a richtextbox In it. The user can type some text and have some editing capability, but I'd like to give the user the option to expand the editor to fullscreen to have more richtextbox editing options. How can I implement a function that will allow me to fullscreen (or atleast create a bigger window) the richtexteditor so the user has better overview over the document and more editing options?
This is ment to be possible in OOB mode.
Full screen wont work as you have limit keyboard input in fullscreen:
Up Arrow
Down Arrow
Left Arrow
Right Arrow
Spacebar
Tab
Page Up
Page Down
Home
End
Enter
What you can do is for example is make your element fill the whole space of your silverlight application by making it the exact size of your RootVisual and adjusting your margins to place it correctly in your application:
XAML:
<UserControl x:Class="SilverlightApplication1.MyRichTextBox"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="FullScreen" Grid.Row="0" Content="FullScreen" Click="FullScreen_Click" />
<RichTextBox Grid.Row="1" />
</Grid>
Code-behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplication1
{
public partial class MyRichTextBox : UserControl
{
private Thickness _oldMargin;
private double _oldHeight = double.NaN;
private double _oldWidth = double.NaN;
private HorizontalAlignment _oldHorizontalAlignment;
private VerticalAlignment _oldVerticalAlignment;
private bool _fullScreen = false;
public MyRichTextBox()
{
InitializeComponent();
}
private void FullScreen_Click(object sender, RoutedEventArgs e)
{
if (_fullScreen)
{
_fullScreen = false;
Margin = _oldMargin;
Width = _oldWidth;
Height = _oldHeight;
}
else
{
_fullScreen = true;
_oldMargin = Margin;
_oldWidth = Width;
_oldHeight = Height;
_oldHorizontalAlignment = HorizontalAlignment;
_oldVerticalAlignment = VerticalAlignment;
FrameworkElement rootVisual = Application.Current.RootVisual as FrameworkElement;
GeneralTransform generalTransform = TransformToVisual(rootVisual);
Point position = generalTransform.Transform(new Point(0, 0));
Width = rootVisual.ActualWidth;
Height =rootVisual.ActualHeight;
Margin = new Thickness(-position.X - 1, -position.Y - 1
, (ActualWidth + position.X) - rootVisual.ActualWidth - 1
, (ActualHeight + position.Y) - rootVisual.ActualHeight - 1);
}
}
}
}
I've got a StackPanel, and it's perfect for laying out columns that the user adds at runtime. But I'd like the columns to be resizable, and I was reading about the GridSplitter control. Here's what I'm wondering:
Is the GridSplitter the wpf replacement for the WinForms splitter? In other words, is this the de facto way to allow the users to resize regions of the window?
Does it only work inside of a Grid? If I have items inside a stackpanel or a dockpanel, can I still use a gridsplitter the way I used the splitter in WinForms?
If I have to use a Grid, how can I make it behave just like a StackPanel? (hope it doesn't come to that)
GridSplitter only works in a Grid and is the easiest way to allow users to resize controls.
What do you mean that you want your grid (with gridsplitters) to behave just like a stackpanel? A stackpanel will exactly fit each of its children while a grid with gridsplitters will leave it up to the user.
Below is a user control which allows items to be added as columns. Between columns are grid splitters. Users can click on Delete button to remove added columns and columns can be added using behind code. Let me know if that's what you were looking for.
User control SmartGrid XAML:
<UserControl x:Class="SmartGridDemo.SmartGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Name="_grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid>
</UserControl>
User control SmartGrid code behind:
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SmartGridDemo
{
public partial class SmartGrid : UserControl
{
public SmartGrid()
{
InitializeComponent();
}
public void Add(UIElement child)
{
int columnIndex = _grid.ColumnDefinitions.Count();
_grid.ColumnDefinitions.Add(
new ColumnDefinition()
{
Width = new GridLength(columnIndex == 0 ? 0 :5)
});
GridSplitter gridSplitter =
new GridSplitter()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
ResizeDirection = GridResizeDirection.Columns,
Background = Brushes.Black
};
_grid.Children.Add(gridSplitter);
Grid.SetColumn(gridSplitter, columnIndex);
Grid.SetRow(gridSplitter, 0);
Grid.SetRowSpan(gridSplitter, 2);
columnIndex++;
_grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
Button button = new Button();
button.Content = "Delete";
button.Tag = new TagTuple() {Child = child, GridSplitter = gridSplitter};
button.Click += new RoutedEventHandler(DeleteButton_Click);
_grid.Children.Add(button);
Grid.SetColumn(button, columnIndex);
Grid.SetRow(button, 0);
_grid.Children.Add(child);
Grid.SetColumn(child, columnIndex);
Grid.SetRow(child, 1);
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
int columnIndex = Grid.GetColumn(button);
TagTuple tagTuple = button.Tag as TagTuple;
_grid.Children.Remove(tagTuple.GridSplitter);
_grid.Children.Remove(tagTuple.Child);
_grid.Children.Remove(button as UIElement);
_grid.ColumnDefinitions.RemoveAt(_grid.ColumnDefinitions.Count() - 1);
_grid.ColumnDefinitions.RemoveAt(_grid.ColumnDefinitions.Count() - 1);
foreach (UIElement child in _grid.Children)
{
int columnIndexForChild = Grid.GetColumn(child);
if (columnIndexForChild > columnIndex)
{
Grid.SetColumn(child, columnIndexForChild - 2);
}
}
}
private class TagTuple
{
public GridSplitter GridSplitter { get; set; }
public UIElement Child { get; set; }
}
}
}
Demo code, add some text in the TextBox and hit Add button to add new columns, XAML:
<Window x:Class="SmartGridDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SmartGridDemo"
Title="SmartGridDemo" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Name="_texBox" Grid.Row="0" Grid.Column="0" />
<Button Content="Add" Click="AddButton_Click" Grid.Row="0" Grid.Column="1" />
<local:SmartGrid x:Name="_smartGrid" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>
</Window>
Demo, behind code:
using System;
using System.Windows;
using System.Windows.Controls;
namespace SmartGridDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
_smartGrid.Add(new TextBlock() { Text = "AAA" });
_smartGrid.Add(new TextBlock() { Text = "BBB" });
_smartGrid.Add(new TextBlock() { Text = "CCC" });
_smartGrid.Add(new TextBlock() { Text = "DDD" });
_smartGrid.Add(new TextBlock() { Text = "EEE" });
_smartGrid.Add(new TextBlock() { Text = "FFF" });
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
_smartGrid.Add(new TextBlock() { Text = _texBox.Text });
}
}
}
Here is an open source WPF SplitContainer:
[http://wpfsplitcontainer.codeplex.com/]