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.
Related
I'm trying to develop something that - with WinForms - would be a proverbial "piece of cake". A dynamic set of data-bound datagrids. That initializes on application start. Sometimes there is a need for one, sometimes for five. At first it looked like too much for XAML. So I'm struggling with it with regular C#. which - with WPF - is insanely unfriendly and I'm hitting the wall again and again.
Or am I doing it all wrong? Is there any correct way to... "duplicate" / "clone" one datagrid designed and closed with XAML and reuse those clones as a dynamic array? Whenever I'm looking for a solution to more and more WPF obstacles (i.e. something as simple (with WinForms) as dynamic row coloring), I find sometimes XAML solutions. plain code solutions are extremely rare. even if try to "translate" XAML to regular code, I miss a lot of properties / methods (or maybe they are named differently). anyway - it's like people these days turn to XAML completely. are arrays of more complex controls that uncommon? I found some examples of button array bound to some table. and that's pretty much it. plus it never worked for an array of datagrids...
This is just a prototype of how binding can yield fast results with minimal code written.
<Window x:Class="testtestz.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ItemsControl ItemsSource="{Binding GridViews}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Data}" BorderBrush="Gray" BorderThickness="1" Margin="5">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Id}" Header="Id"/>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name"/>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
This is the code behind.
using System.Collections.Generic;
using System.Windows;
namespace testtestz
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<object> myData = new List<object>()
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mary" },
new { Id = 3, Name = "Anna" },
};
GridViews.Add(new MyGrid { Data = myData});
GridViews.Add(new MyGrid { Data = myData });
GridViews.Add(new MyGrid { Data = myData });
DataContext = this;
}
public List<MyGrid> GridViews { get; } = new List<MyGrid>();
}
public class MyGrid
{
public IEnumerable<object> Data { get; set; }
}
}
Keep in mind you can bind almost anything you like so MyGrid class could very well have all the properties needed to create these grids. So for instance, you can have column definitions such as Header texts, column widths and such...
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>
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*" />
In Silverlight 3, I am tring to produce a basic, populated DataGrid, but the DataGrid is displaying 4 empty rows.
The code snippets for the page.xaml and page.xaml.cs are below.
I think the data is correctly contained in e.Results, because when I step through the codebehind, the web service is returning the List correctly populated, e.Results shows a count of 4, and the DataGrid displays four empty rows. (I have not yet figured out how to see what exactly is in e.Results)
Here is the relevant code in page.xaml.cs:
void Page_Loaded(object sender, RoutedEventArgs e)
{
Service1Client service = new Service1Client();
service.GetAccountsCompleted += new
EventHandler<GetAccountsCompletedEventArgs>(service_GetAccountsCompleted );
service.GetAccountsAsync();
}
void service_GetAccountsCompleted(object sender, GetAccountsCompletedEventArgs e)
{
this.grdData.ItemsSource = e.Result;
}
Here is the xaml definition of the DataGrid:
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="BegSilver.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid x:Name="grdData" Margin="15" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding acpk}" Header="acpk"></data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding acctnumber}" Header="acctnumber"></data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding name}" Header="name"></data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding type}" Header="type"></data:DataGridTextColumn>
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
</UserControl>
Any help would be greatly appreciated.
I had the exact same problem. It turned out that the Foreground text color and background colors of the cells were the same. Just change those and you should be good. There's some examples here: http://silverlight.net/forums/t/42372.aspx
Add a foreground color style to your datagrid to see the text. Something like this:
<UserControl.Resources>
<Style x:Key="gridStyle" TargetType="Controls:DataGrid">
<Setter Property="Foreground" Value="Black" />
</Style>
</UserControl.Resources>
<Controls:DataGrid x:Name="grid" AutoGenerateColumns="True" Style="{StaticResource gridStyle}">
...
The column names are case sensitive. Are you sure it is
Binding="{Binding acpk}"
and not
Binding="{Binding Acpk}"
You can't perform binding like that directly from e.Result.
<data:DataGridTextColumn Binding="{Binding acpk}" Header="acpk">
</data:DataGridTextColumn>
You're on the right track working with e.Result but you really should put the results into a collection then work with that. I know it's redundant but it seems that a collection of a collection or a list of a collection etc is how it's done the "Silverlight way"
Try something like this.
Create a class. This will process your results when looping through e.Result.
public class Recording
{
public Recording() {
}
public Recording(string artistName, string cdName, DateTime release)
{ Artist = artistName; Name = cdName; ReleaseDate = release; }
public string Artist { get; set; }
public string Name { get; set; }
public DateTime ReleaseDate { get; set; }
// Override the ToString method.
public override string ToString()
{
return Name + " by " + Artist + ", Released: " + ReleaseDate.ToShortDateString();
}
}
In the code behind where you have called your service create a collection. This will collect e.Result content and pass it to the class mentioned.
public ObservableCollection<Recording> MyMusic = new ObservableCollection<Recording>();
In the completed args event handler add something like this.
private void service_GetAccountsCompleted(object sender, GetAccountsCompletedEventArgs e)
{
foreach (var item in e.Result)
{
MyMusic.Add(new Recording(item.acctnumber, item.acpk, new DateTime(2011, 4, 18)));
grdData.DataContext = MyMusic;
}
I have used this for reference and I think it's worth a look. It shows how to bind in multiple ways and helped me when learning more about data binding.
http://www.silverlight.net/learn/quickstarts/bindingtocontrols/
A guess here, but I would venture that rather than setting the itemssource property, you should be setting the DataContext property. ItemsSource is for collection based controls such as a ListBox etc, and is used to define what collection to use to populate the items.
My guess is that you populated an object (Account) from a WCF service
If so, set this.grdData.DataContex= e.Result; in your GetAccountsCompleted handler
Good Post here sumarrizing the differences between ItemsSource and DataContext
http://anyedotnet.blogspot.com/2008/11/datacontext-vs-itemssource.html