Using C#.Net 4.5, Visual Studio 2012 Ulti, WPF.
I've got some old win-forms code that i wanted to do in this new WPF app.
code is the following:
DataGridViewImageCell pNew = new DataGridViewImageCell();
ParetoGrid.Columns.Add(new DataGridViewImageColumn() { CellTemplate = pNew, FillWeight = 1, HeaderText = "pNew", Name = "pNew", Width = 30 });
ParetoGrid.Columns["pNew"].DisplayIndex = 18;
3 lines of code to add a column that can handle images. In WPF I've seen its a bit different. Do i need to add an "image column"? or does WPF columns support images? or is there another 3 liner solution that is simply different syntax?
Thanks for the help guys
See this answer:
Image Column in WPF DataGrid
<DataGridTemplateColumn Header="Image" Width="SizeToCells"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
To add a column in code after:
DataGridTextColumn textColumn1 = new DataGridTextColumn();
textColumn1.Header = "Your header";
textColumn1.Binding = new Binding("YourBindingField");
dg.Columns.Add(textColumn1);
Use DataGridTemplateColumn to add a custom column
See: How do I show image in wpf datagrid column programmatically?
Here is what I did.
Add a datatemplate in your datagrid with image control like this
<DataGridTemplateColumn Header="File Type" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Height="25" Width="50" Source="{Binding FileIcon}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
As you can see my I am binding Image with a property named "FileIcon" that is used in class Version like this
public class Version
{
public string FileIcon { get; set; }
}
Now only this you have to do is bind provide a path to "FileIcon" and update ItemSource of DataGrid like this
ObservableCollection<Version> items = new ObservableCollection<Version>();
items.Add(new Version()
{
FileIcon = "/AssemblyName;component/Images/eye.png",
});
YourDataGrid.ItemsSource = null;
YourDataGrid.ItemsSource = items;
Here is MainWindow.xaml's code just simple for better understanding
`
<Window x:Class="Pic_in_Datagrid.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:Pic_in_Datagrid"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dt1" ColumnWidth="*" AutoGenerateColumns="False">
</DataGrid>
</Grid>
</Window>
After it here is my MainWindow.xaml.cs's code for image or text for adding in datadrid dynamically...
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
namespace Pic_in_Datagrid
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public StudentData stu_data { get; set; }
public MainWindow()
{
InitializeComponent();
stu_data = new StudentData();
List<StudentData> stu = new List<StudentData>();
stu.Add(new StudentData() { image = toBitmap(File.ReadAllBytes(#"D:\1.jpg")), stu_name = "abc" });
stu.Add(new StudentData() { image = toBitmap(File.ReadAllBytes(#"D:\1.jpg")), stu_name = "def" });
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(System.Windows.Controls.Image));
Binding bind = new System.Windows.Data.Binding("image");//please keep "image" name as you have set in your class data member name
factory.SetValue(System.Windows.Controls.Image.SourceProperty, bind);
DataTemplate cellTemplate = new DataTemplate() { VisualTree = factory };
DataGridTemplateColumn imgCol = new DataGridTemplateColumn()
{
Header = "image", //this is upto you whatever you want to keep, this will be shown on column to represent the data for helping the user...
CellTemplate = cellTemplate
};
dt1.Columns.Add(imgCol);
dt1.Columns.Add(new DataGridTextColumn()
{
Header = "student name",
Binding = new Binding("stu_name") //please keep "stu_name" as you have set in class datamember name
});
dt1.ItemsSource = stu;
}
public static BitmapImage toBitmap(Byte[] value)
{
if (value != null && value is byte[])
{
byte[] ByteArray = value as byte[];
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
bmp.StreamSource = new MemoryStream(ByteArray);
bmp.EndInit();
return bmp;
}
return null;
}
}
public class StudentData
{
public BitmapImage image { get; set; }
public string stu_name { get; set; }
}
}
The above all code is taken from different resources... Thanks to them who developed and shared these codes...
You may try add Image to DataGridTextColumn via pattern bellow. You may sorting and copy to clipboard works well. Use your converter, or binding to your property.
<DataGridTextColumn Header="Level" IsReadOnly="True" Binding="{Binding Level,Converter={StaticResource LogLevelStringConverter}}" >
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<Grid Background="{TemplateBinding Background}" >
<ContentPresenter VerticalAlignment="Center" Margin="20,0,0,0" HorizontalAlignment="Left" />
<Image Grid.Column="0" Width="18" Height="18" Source="{Binding Level,Converter={StaticResource LogLevelIconConverter}}" HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Related
Working on learning WPF by converting one of my applications over from WinForms
What is the WPF way of doing the following
DataTable _current = _connections.Copy();
BindingSource _bs = new BindingSource();
bs.DataSource = _current;
bs.Filter = "Client = '" + _selectedClient + "'";
After the new DataTable table is filtered down, then I would need to assign the binding source to a DataGrid.
Update 2
I have added the following
public ObservableCollection<SupportConnectionData> _supportConnections = new ObservableCollection<SupportConnectionData>();
turn the datatable given into ObservableCollection
DataTable _dt = Global.RequestSupportConnections(_token);
_dt = Crypto.DecryptDataTable(_dt);
ObservableCollection<SupportConnectionData> _connections = new ObservableCollection<SupportConnectionData>();
foreach (DataRow _row in _dt.Rows)
{
SupportConnectionData _supportConnection = new SupportConnectionData()
{
_client = _row["Client"].ToString(),
_server = _row["Server"].ToString(),
_user = _row["User"].ToString(),
_connected = _row["Connected"].ToString(),
_disconnected = _row["Disconnected"].ToString(),
_reason = _row["Reason"].ToString(),
_caseNumber = _row["CaseNumber"].ToString()
};
_connections.Add(_supportConnection);
}
//let me assign new collection to bound collection
App.Current.Dispatcher.BeginInvoke((Action)(() => { _supportConnections = _connections; }));
//this allows it to update changes to ui
dgSupportConnections.Dispatcher.BeginInvoke((Action)(() => { dgSupportConnections.DataContext = _supportConnections; }));
XAML
<DataGrid x:Name="dgSupportConnections" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" AutoGenerateColumns="False" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Client" Binding="{Binding _client}"/>
<DataGridTextColumn Header="Server" Binding="{Binding _server}"/>
<DataGridTextColumn Header="User" Binding="{Binding _user}"/>
<DataGridTextColumn Header="Connected" Binding="{Binding _connected}"/>
<DataGridTextColumn Header="Disconnected" Binding="{Binding _disconnected}"/>
<DataGridTextColumn Header="Reason" Binding="{Binding _reason}"/>
<DataGridTextColumn Header="Case Number" Binding="{Binding _caseNumber}"/>
</DataGrid.Columns>
</DataGrid>
get your database objects (or however you get them) into a collection (say MyCollection as ObservableCollection of Type) or collection view source then bind to that. IN wpf you have to work with the context of the class that the xaml view is bound to. So if the immediate context of the datagrid is the code behind then you would add this line to the datagrid to bind to the collection:
<DataGrid ItemsSource="{Binding MyCollection}" / >
In win forms you can assign the collections to the datagirid in code, but in WPF you declare the binding in the xaml and the "WPF engine" takes care of the rest. There is a bit of a learning curve but it is really flexible and in my opinion reduces code.
But this raises a larger discussion about the architecture of your application. I would suggest looking at MVVM to create a decoupling or separation of concerns between the model (your data), the view (user interface), and the ViewModel (which handles your business logic). THis will make you application more maintainable and testable.
EDIT 1: EXAMPLE
xaml of the window:
<Window x:Class="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>
<DataGrid x:Name="MyDataGrid" AutoGenerateColumns="True" ItemsSource="{Binding MyObjectCollection}" DataContext="{Binding}" />
</Grid>
</Window>
Code behind of the Xaml window:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Collections.ObjectModel;
class MainWindow
{
public ObservableCollection<MyObject> MyObjectCollection = new ObservableCollection<MyObject>();
public MainWindow()
{
// This call is required by the designer.
InitializeComponent();
// Add any initialization after the InitializeComponent() call.
for (i = 1; i <= 10; i++) {
MyObject newObject = new MyObject {
age = i,
name = "Full Name"
};
MyObjectCollection.Add(newObject);
}
MyDataGrid.ItemsSource = MyObjectCollection;
}
}
public class MyObject
{
public string name { get; set; }
public string age { get; set; }
}
While this example works for learning, I DO NOT suggest this method for production apps. I think you need to look into MVVM, or MVC in order to have an application that is maintainable and testable.
I cannot bind my sample data to textblocks in stackpanel, which I defined in resources. I think that I use style in wrong way, because I receive toString() method instead of class binded fields.
That's my resources with defined style:
<UserControl.Resources>
<ItemsPanelTemplate x:Key="VirtualizingStackPanelTemplate">
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
<ListView x:Key="ListBoxTemplate" HorizontalAlignment="Left" ScrollViewer.HorizontalScrollBarVisibility="Visible">
<ListView.ItemTemplate>
<DataTemplate>
<!--<ListBoxItem Background="DarkOrchid" Margin="1,1, 5,5" Height="400" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch">-->
<StackPanel>
<TextBlock FontSize="30" Text="{Binding Title}"/>
<TextBlock FontSize="20" Text="{Binding Desc}"/>
</StackPanel>
<!--</ListBoxItem>-->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl.Resources>
Here is my method in which i add listview programatically:
long rowCount = ContentGridFullView.RowDefinitions.LongCount();
if (rowCount > 8) return;
var c1 = new RowDefinition { Height = new GridLength(1, GridUnitType.Star) };
ContentGridFullView.RowDefinitions.Add(c1);
rowCount = ContentGridFullView.RowDefinitions.LongCount();
TextBlock tb = new TextBlock {Text = "TEXTBLOCK ITEM = " + (rowCount - 1), FontSize = 40};
Viewbox vb = new Viewbox { Child = tb };
if (rowCount > 8) return;
Grid.SetRow(vb, Convert.ToInt32(rowCount-1));
Grid.SetColumn(vb, 1);
ListView lb = new ListView();
lb.Style = Resources["ListBoxTemplate"] as Style;
lb.ItemsPanel = (ItemsPanelTemplate) Resources["VirtualizingStackPanelTemplate"];
var products = new ObservableCollection<Product>() { new Product("ASDASDSADAS", "VCBVCBVCBVCBC"), new Product("ASDASDSADAS", "VCBVCBVCBVCBC"), new Product("ASDASDSADAS", "VCBVCBVCBVCBC"), new Product("ASDASDSADAS", "VCBVCBVCBVCBC") };
lb.ItemsSource = products;
ContentGridFullView.Children.Add(lb);
ContentGridFullView.Children.Add(vb);
Grid.SetRow(lb, Convert.ToInt32(rowCount - 1));
Grid.SetColumn(lb, 2);
And my short class that I want to bind:
public class Product
{
public string Title { get; set; }
public string Desc { get; set; }
public Product(string title, string desc)
{
Title = title;
Desc = desc;
}
public override string ToString()
{
return "I see that message instead of Title and Desc";
}
}
Can someone tell me what's wrong with this code? Thank you.
Create your Observable collection as a property (getter/setter):
ObservableCollection<Product> _products;
public ObservableCollection<Product> products
{
get{return _products;}
set
{
_products=value;
PropertyChanged("products");
}
}
The property changed event will be need to indicate that the collection has changed,its needed when your using ObservableCollection. You'll need to read more about it.You can add items to the products collection by using :
products.Add(Product_object)
And your xaml code will have the itemsSource as follows:
<ListView x:Key="ListBoxTemplate" HorizontalAlignment="Left" ScrollViewer.HorizontalScrollBarVisibility="Visible" ItemsSource="{Binding products}">
<ListView.ItemTemplate>
<DataTemplate>
<!--<ListBoxItem Background="DarkOrchid" Margin="1,1, 5,5" Height="400" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch">-->
<StackPanel>
<TextBlock FontSize="30" Text="{Binding Title}"/>
<TextBlock FontSize="20" Text="{Binding Desc}"/>
</StackPanel>
<!--</ListBoxItem>-->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The following statement is important in your xaml code so that your xaml code will know where to look for the data.
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=x}
First try and create a static list and check if data is getting initialized properly and then you can try creating listviews dynamically. But the code above will be the same thing you will have to do create dynamic listviews.
This is a really basic requirement, but I'm stuck! For WPF/.Net - I just want to dynamically draw into a Canvas column in my ListView. One failed attempt:
<ListView name="myGridView">
<GridViewColumn Header="ColumnA" DisplayMemberBinding="{Binding Path=ColumnA}" />
<GridViewColumn DisplayMemberBinding="{Binding Path=ColumnB}">
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SUSPECT!
<Canvas Name="canvasColumn" Width="100" Height="20" />
</GridViewColumn>
</GridView>
Inside my code, I have a class "MyData" with fields bound to the non-canvas ListView columns. I loop through some "Whatever"s creating items in the ListView:
foreach (Whatever whatever in whatevers)
{
MyData myData = new MyData();
myData.ColumnA = whatever.A;
myData.ColumnB = new Canvas();
Line line = new Line();
line.Stroke = System.Windows.Media.Brushes.Black;
line.X1 = line.Y1 = 1;
line.X2 = line.Y2 = 100;
line.StrokeThickness = 1;
myData.ColumnB.Children.Add(line);
myListView.Items.Add(myData);
}
This DOES NOT work: every row in the on-screen canvas column displays the text "System.Windows.Controls.Canvas". Not terribly surprising - I've bound the column in the same way as the text columns and some toString conversion of the typename seems to kick in. But, I've tried a few other things and just can't get the Canvas displayed.
I have also tried removing the column binding marked "SUSPECT" above, and myData's ColumnB field, seeking a way to refer to the canvas widgets via the listview, i.e. something of the form:
myListView.reference-to-new-row-and-canvas-column = theNewCanvasIDrewOn;
Some of my searches have turned up ugly messes of Styles, ItemPanel configs etc.: please - if that's necessary, I at least hope it can be kept minimal....
Any guidance greatly appreciated.
Cheers,
Tony
UPDATE
For my purposes, the minimal solution appears to be adding a DataTemplate to App.xaml's Application.Resources tag:
<DataTemplate x:Key="myTemplate">
<Canvas Width="60" Height="20" Background="Red" ClipToBounds="True" >
<ContentPresenter Content="{Binding myCanvasField}" />
</Canvas>
</DataTemplate>
and defining a GridViewColumn as:
<GridViewColumn CellTemplate="{StaticResource myTemplate}" Header="title" />
Thanks to Dean for pointing me in the right direction, and to Binding to Canvas for canvas-specific details. I then "draw on" the Canvas property member of the object I add to the ListView.
you need to use a CellTemplate rather than a Canvas directly
http://msdn.microsoft.com/en-us/library/system.windows.controls.gridviewcolumn.celltemplate.aspx
You could impliment the TaskVisualizer as a custom control and then just host that in your list template. This separates out your task visualization code from your global UI code. This has the advantage that its easy to reuse the task visualation else where - eg you could easily show the same graphic in a tooltip when hovering over a task in some other view.
here's my take on it. The idea is to use a mini DSL to exchange the information between your canvas and your business objects.
XAML:
<Window x:Class="DrawInCanvas.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DrawInCanvas"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Item1}" />
<DataGridTemplateColumn Header="Bar">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Canvas HorizontalAlignment="Left"
Height="20"
local:CanvasDrawing.Drawing="{Binding Item2}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DrawInCanvas
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// just a sample
Dictionary<int, string> barDefinitions = new Dictionary<int, string>(3)
{
{ 1, "100$red" },
{ 2, "220$yellow" },
{ 3, "40$blue" }
};
this.g.ItemsSource =
Enumerable.Range(1, 3).Select(t =>
new Tuple<int, string>(t, barDefinitions[t]));
}
}
public class CanvasDrawing : DependencyObject
{
public static readonly DependencyProperty DrawingProperty =
DependencyProperty.RegisterAttached("Drawing",
typeof(string),
typeof(CanvasDrawing),
new PropertyMetadata(new PropertyChangedCallback((o, e) =>
{
CanvasDrawing.Draw((Canvas)o, (string)e.NewValue);
})));
public static void SetDrawing(Canvas canvas, string drawing)
{
canvas.SetValue(CanvasDrawing.DrawingProperty, drawing);
}
public static string GetDrawing(Canvas canvas)
{
return (string)canvas.GetValue(CanvasDrawing.DrawingProperty);
}
private static void Draw(Canvas canvas, string drawing)
{
string[] parts = drawing.Split("$".ToCharArray());
canvas.Width = double.Parse(parts[0]);
canvas.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString(parts[1]));
}
}
}
<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Change}" Width="Auto"/>
When the value of Change updates, its column doesn't update to fit the new value. So the column stays too small and the value is clipped.
Any ideas?
The DataGrid will increase column sizes to fit as the data becomes longer, but it does not automatically decrease column sizes when the length of the data decreases. In your example, you're right aligning the 'Change' column, and using the rest of the space for the 'Name' column.
Now, when a 'Change' property grows large enough that it should increase the column's width, the 'Name' column is refusing to shrink to accommodate, so you have to force a refresh yourself.
The following steps should do this for you (I've included a sample app to demo):
1) In your DataGridTextColumn Bindings (all except your * sized column) set NotifyTargetUpdated=True.
2) On your DataGrid, add a handler to the TargetUpdated event.
3) In your TargetUpdated event handler:
-- a) Set the DataGrid's * sized column's width to 0.
-- b) Call the UpdateLayout() method on the DataGrid.
-- c) Set the DataGrid's * sized column's width back to new DataGridLength(1, DataGridLengthUnitType.Star)
Example XAML:
<Window x:Class="DataGridTest.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">
<Window.Resources>
<CollectionViewSource x:Key="MyObjectCollection" />
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Click to Make Item 1s Text Longer" Click="Button_Click" />
<Grid>
<DataGrid x:Name="dg" ItemsSource="{Binding Source={StaticResource MyObjectCollection}}" AutoGenerateColumns="False" TargetUpdated="dg_TargetUpdated">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding First}" Width="1*"/>
<DataGridTextColumn Binding="{Binding Last, NotifyOnTargetUpdated=True}" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
</Window>
Example Code Behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
namespace DataGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<MyObject> myObjectList = new ObservableCollection<MyObject>();
public MainWindow()
{
InitializeComponent();
(this.FindResource("MyObjectCollection") as CollectionViewSource).Source = this.myObjectList;
this.myObjectList.Add(new MyObject() { First = "Bob", Last = "Jones" });
this.myObjectList.Add(new MyObject() { First = "Jane", Last = "Doe" });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.myObjectList[0].Last = "BillyOBrian";
}
private void dg_TargetUpdated(object sender, DataTransferEventArgs e)
{
dg.Columns[0].Width = 0;
dg.UpdateLayout();
dg.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
}
public class MyObject : INotifyPropertyChanged
{
private string firstName;
public string First
{
get { return this.firstName; }
set
{
if (this.firstName != value)
{
this.firstName = value;
NotifyPropertyChanged("First");
}
}
}
private string lastName;
public string Last
{
get { return this.lastName; }
set
{
if (this.lastName != value)
{
this.lastName = value;
NotifyPropertyChanged("Last");
}
}
}
public MyObject() { }
#region -- INotifyPropertyChanged Contract --
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Contract
}
}
i have had similar problem with my listview, the solution i found on how-to-autosize-and-right-align-gridviewcolumn-data-in-wpf here on stackoverflow.
In my case it was adding this piece of code into collectionchanged event handler of the observable collection the list view was bound to:
void listview_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
// this is a listview control
GridView view = this.View as GridView;
foreach(GridViewColumn c in view.Columns) {
if(double.IsNaN(c.Width)) {
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
It works for me, although sometimes the user can notice "blink" over the columns.
WPF will just resize a datagrid's column width set to Auto if needed, i.e: the content cannot be displayed entirely. So when the content's width shrinks, the column does not resize as the content can still been seen entirely.
the only way I can see to force wpf to recalculate the columns' widths would be to force them all to 0 and then back to auto in the code behind, with one or two updateLayout() thrown in, but this is not very nice programming :-/
basically, in your code behind:
foreach (DataGridColumn c in dg.Columns)
c.Width = 0;
// Update your DG's source here
foreach (DataGridColumn c in dg.Columns)
c.Width = DataGridLength.Auto;
and you probably need a dg.UpdateLayout() or two somewhere in there (after the update and the setting back to auto probably)
One way you could solve this is by defining the width property of the column in a style setting and binding that setting to a property of the object you are binding to.
<DataGridTextColumn Binding="{Binding Change}" ElementStyle="{StaticResource ChangeColumnStyle}"/>
In your ResourceDictionary:
<Style TargetType="{x:Type DataGridTextColumn }" x:Key="ChangeColumnStyle">
<Setter Property="Width" Value="{Binding ColumnWidth}"
</Style>
ColumnWidth should be a property of your object. Now if you update this property from the setter of your 'Change' property (by using some self-defined algorithm, taking stuff like font into account), and calling:
RaisePropertyChanged("ColumnWidth");
It should update your column width.
public int Change
{
get { return m_change; }
set
{
if (m_change != value)
{
m_change = value;
ColumnWidth = WidthAlgo(numberOfCharacters);
RaisePropertyChanged("Change");
RaisePropertyChanged("ColumnWidth");
}
}
}
Have you tried this?
<DataGridTextColumn Binding="{Binding Path= Id}" Header="ID" IsReadOnly="True" Width="1*" />
I wanna show an image in WPF that is created by a process,
e.g : we have a method that is named createWPFImage()
Image createWPFImage() { ... }
So, the output of createWPFImage() is an Image.
In the XAML code, we have a something like below :
<StackPanel.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Width="64" Height="64" Margin="0 2 4 0" />
<TextBlock Text="{Binding Path=Description}" VerticalAlignment="Center" />
</StackPanel>
</StackPanel.ToolTip>
Now, How can I bind the output of createWPFImage() to the Image in XAML code ?
I would be appreciate if you guide me.
Say you have class "MyClass" with method "CreateWpfImage" (see example below).
In your XAML you can create MyClass, and then call CreateWpfImage, using ObjectDataProvider in a Resources section (See Bea Stollnitz blog article ObjectDataProvider).
XAML
<Window x:Class="MyApplicationNamespace.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MyApplicationNamespace="clr-namespace:MyApplicationNamespace"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type MyApplicationNamespace:MyClass}" x:Key="MyClass" />
<ObjectDataProvider ObjectInstance="{StaticResource MyClass}" MethodName="CreateWpfImpage" x:Key="MyImage" />
</Window.Resources>
<StackPanel>
<Image Source="{Binding Source={StaticResource MyImage}, Path=Source}"/>
</StackPanel>
Example MyClass code to create an image for the XAML to use -
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyApplicationNamespace
{
public class MyClass
{
public Image CreateWpfImpage()
{
GeometryDrawing aGeometryDrawing = new GeometryDrawing();
aGeometryDrawing.Geometry = new EllipseGeometry(new Point(50, 50), 50, 50);
aGeometryDrawing.Pen = new Pen(Brushes.Red, 10);
aGeometryDrawing.Brush = Brushes.Blue;
DrawingImage geometryImage = new DrawingImage(aGeometryDrawing);
Image anImage = new Image();
anImage.Source = geometryImage;
return anImage;
}
}
}
If you have a path to your image and just want to be able to change the image on the fly, then bind to a dependency property of type string and in your method, set the value of the dependency property.
<Image Source="{Binding MyImagePath}" />
public static readonly DependencyProperty MyImagePathProperty = DependencyProperty.Register("MyImagePath", typeof(string), typeof(ClassName), new PropertyMetadata("pack://application:,,,/YourAssembly;component//icons/icon1.png"));
public string MyImagePath
{
get { return (string)GetValue(MyImagePathhProperty); }
set { SetValue(MyImagePathProperty, value); }
}