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]));
}
}
}
Related
I am a reeeally noob beginner WPF developer, and getting the hang of c#.
I am creating an app, where I need a knob Button and a TextBox Display, where the knob adjusts the text from the display, and the display, if text is changed, updates the knob position.
Inage example of my application
I've managed to create the Knob Button, which spins when clicked and dragged, and also managed to bind it's value to the TextBox, it displays the value perfectly, but I can't make the TextBox Text update the Knob's position, which is defined by Angle variable (from the RotateTransform thing), the code is as follows:
<UserControl x:Class="quaselaeuespero.VolumeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:quaselaeuespero"
mc:Ignorable="d"
d:DesignHeight="154" d:DesignWidth="148">
<Grid>
<Image Name="aPorradoknob" Source="Knob.png" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:VolumeControl}}, Path=Angle}"/>
</Image.RenderTransform>
</Image>
</Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 quaselaeuespero
{
/// <summary>
/// Interaction logic for VolumeControl.xaml
/// </summary>
public partial class VolumeControl : UserControl
{
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(VolumeControl), new UIPropertyMetadata(0.0));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public VolumeControl()
{
InitializeComponent();
this.Angle = 120;
this.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
this.MouseUp += new MouseButtonEventHandler(OnMouseUp);
this.MouseMove += new MouseEventHandler(OnMouseMove);
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(this);
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
// Get the current mouse position relative to the volume control
Point currentLocation = Mouse.GetPosition(this);
// We want to rotate around the center of the knob, not the top corner
Point knobCenter = new Point(this.ActualHeight / 2, this.ActualWidth / 2);
// Calculate an angle
double radians = Math.Atan((currentLocation.Y - knobCenter.Y) /
(currentLocation.X - knobCenter.X));
this.Angle = radians * 180 / Math.PI;
// Apply a 180 degree shift when X is negative so that we can rotate
// all of the way around
if (currentLocation.X - knobCenter.X < 0)
{
this.Angle += 180;
}
if(this.Angle >= -90 && this.Angle <= -45)
{
this.Angle = 270;
}
if (this.Angle >= -45 && this.Angle <= 0)
{
this.Angle = 1;
}
this.Angle = Math.Round(this.Angle, 1);
}
}
}
}
The Knob is <VolumeControl/> and Display is <DisplayBPM/>, in the main Window I tried to bind them both:
<Window x:Class="quaselaeuespero.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:quaselaeuespero"
mc:Ignorable="d"
Title="MainWindow" Height="540" Width="960">
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Background.png"/>
</Grid.Background>
<local:VolumeControl x:Name="Knobão" Margin="123,240,675,111" RenderTransformOrigin="0.5,0.5" Angle="{Binding ElementName=BPMDisplay, Path=BPM, UpdateSourceTrigger=Explicit}"/>
<local:DisplayBPM x:Name="BPMDisplay" BPM="{Binding ElementName=Knobão, Path=Angle, UpdateSourceTrigger=Explicit}" Margin="68,153,656,274" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
The following is the code for DisplayBPM:
<UserControl x:Class="quaselaeuespero.DisplayBPM"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:quaselaeuespero"
mc:Ignorable="d"
d:DesignHeight="79
" d:DesignWidth="229"
Name="Display">
<Grid Margin="0">
<TextBox x:Name="BPMTexto" Text="{Binding ElementName=Display, Path=BPM}" HorizontalAlignment="Right" Margin="0,0,4,0" Width="222" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" Foreground="#FFCF1D1D" FontSize="80" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="DS-Digital" RenderTransformOrigin="0.5,0.5" CaretBrush="#FFCF1D1D">
<TextBox.Background>
<ImageBrush ImageSource="Display BPM.png" Stretch="Uniform"/>
</TextBox.Background>
</TextBox>
</Grid>
</UserControl>
and the DisplayBPM.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 quaselaeuespero
{
/// <summary>
/// Interaction logic for DisplayBPM.xaml
/// </summary>
public partial class DisplayBPM : UserControl
{
private void BPMTexto_TextChanged(object sender, EventArgs e)
{
BPM = Convert.ToDouble(BPMTexto.Text);
}
public static readonly DependencyProperty BPMProperty =
DependencyProperty.Register("BPM", typeof(double), typeof(DisplayBPM), new UIPropertyMetadata(0.0));
public double BPM
{
get { return (double)GetValue(BPMProperty); }
set { SetValue(BPMProperty, value); }
}
public DisplayBPM()
{
InitializeComponent();
BPM = 1;
}
}
}
The problem is that BPM (variable from DisplayBPM, from which TextBox gets its input) doesn't seem to be changed, and if it is changed, it is not changing Angle (variable from RotateTransform that determines the Knob position). Can anyone help me? I know there are probably tons of basic problems, it would really help me if you could explain them to me. Thank you so much!
To start, making a custom user control that has Dependency Properties is not the solution for every problem in WPF.
WPF Apps are primarily architected with MVVM: Model - View - ViewModel
With that stated for your specific need I would keep the VolumeControl as that is the correct way to create custom UserControls that have custom DependencyProperties
I would then delete the DisplayBPM class as it is not needed.
I would setup a ViewModel to interact between your controls that contains a single BPM string property.
Here is an example ViewModel I would use:
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _bpm;
public string BPM
{
get => _bpm;
set
{
_bpm = value;
RaisePropertyChanged(nameof(BPM));
}
}
public void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
As a side note, I would suggest reading up on INotifyPropertyChanged
as there are many libraries out there that you can use to help with
WPF and MVVM
I would then setup the Window with the VolumeControl and just a TextBox to hold the BPM value. Both of these should have a {Binding BPM, Mode=TwoWay} so that you pass the BPM value between controls.
You can then decide on the TextBox binding if you want the value to take as the user is typing or after the user leaves the field (usually with the Tab key). To have the value update the VolumeControl as the user is typing have UpdateSourceTrigger=PropertyChanged in the binding.
See my example here:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="Background.png"/>
</Grid.Background>
<local:VolumeControl
x:Name="Knobão"
Margin="123,240,675,111"
RenderTransformOrigin="0.5,0.5"
Angle="{Binding BPM, Mode=TwoWay}" />
<TextBox
Text="{Binding BPM, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="68,153,656,274"
MinWidth="222"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox.Background>
<ImageBrush
ImageSource="Display BPM.png"
Stretch="Uniform" />
</TextBox.Background>
</TextBox>
</Grid>
If you are not familiar with how ViewModels and DataContext work within WPF I would recommend reading up on that as that is the primary way to setup an MVVM architecture for WPF apps.
I'm trying to design a very basic application for windows phone (C#/XAML). At first, I used the hub template, but I got lost so I decided to go step by step and started from a blank app and added a hub.
I managed to bind data between two distinct pages, with line codes such as TextBox.DataContext = DataContext ... but I'd like to bind data properly to hub sections, which is not as easy since hub elements lie in "DataTemplate" which cannot be accessed.
I spent the last two weeks reading documentation, tutorials, etc... Now I've read so many different sources that I am totally lost and do not know what I should do.
Here is my app : A "Players" class (player name and score); and a simple page with a hub control that has 2 sections.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace App1
{
class Players
{
private string playerName;
public string PlayerName
{
get { return playerName; }
set { playerName = value; }
}
private int playerScore;
public int PlayerScore
{
get { return playerScore; }
set { playerScore = value; }
}
}
}
The code behind is very basic, I just created a list of that I populated with only two players. All I want to do, for now, is to understand how the data binding can work.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace App1
{
public sealed partial class MainPage : Page
{
public MainPage()
{
List<Players> players = new List<Players>();
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Players player1 = new Players(); Players player2 = new Players();
player1.PlayerName = "Vince"; player1.PlayerScore = 2; player2.PlayerName = "Mike"; player2.PlayerScore = 42;
players.Add(player1); players.Add(player2);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
}
All I'd like to do, for now, is to understand how the data binding works.
For example, I would like to have a TextBox in my hub that would display player1.PlayerName.
My XAML code, as of today looks as :
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:Players x:Key="PlayerDataSource"/>
</Page.Resources>
<Page.DataContext>
<Binding Source="{StaticResource PlayerDataSource}"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Hub x:Name="Hub1" Grid.Row="1" DataContext="Players">
<HubSection x:Name="HubSec1">
<DataTemplate>
<StackPanel>
<TextBlock Text="Hello"></TextBlock>
<TextBox x:Name="tb1"></TextBox>
</StackPanel>
</DataTemplate>
</HubSection>
<HubSection x:Name="HubSec2">
<DataTemplate>
<StackPanel>
<TextBlock Text="Section2"></TextBlock>
<TextBlock Text="Trying to bind"/>
<TextBlock Text="{Binding PlayerName}"/>
</StackPanel>
</DataTemplate>
</HubSection>
</Hub>
</Grid>
I know how to bind a full list to an element such as a listbox (with a ItemsSource={Binding} in XAML + ListBox.DataContext = players in code behind), but here I would like to display only one given element of my players...
I have tried to add a xmlns:data="clr-namespace" but this does not seem to be working (the IDE does not propose any auto-completion)
I am probably doing something wrong somewhere... but can't figure out where exactly. As mentionned above, I have tried so many different options that I am totally lost now...
OK, I finally found out what was wrong in my code.
First, in the xaml, for each list box, make sure to add the binding
<ListBox x:Name="lb" ItemsSource="{Binding}" Grid.Column="1">
Then, for example, to bind to a textbox, I simply added :
<TextBlock Text="{Binding PlayerName}" FontSize="32" Grid.Column="0"/>
And in the code behind, a simple :
MainHub.DataContext = players;
And that was it, everything binds perfectly now. Thanks for your help
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.
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>
I would like to add some Checkbox in a Grid control dynamically when the window is loaded in my C# Desktop application. How many times the checkbox will appear depends on the number of entries in a table. Here, I used LINQ To SQL class.
The Grid control is defined in XAML.
...
<Grid Name="grid1">
<!-- here i would like to show all check box -->
</Grid>
...
Code behind file:
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.Shapes;
// class declaration ...
...
private void course_Loaded(object sender, RoutedEventArgs e)
{
List<Course> courses = ldc.Courses.ToList();
foreach (var c in courses)
{
CheckBox cb = new CheckBox();
cb.Name=c.CourseID.ToString();
cb.Content = c.CourseID.ToString();
//grid1.Controls.Add(cb); does not work. what to do here?
}
}
This code is not working. Any suggession?
Thankyou.
I suggest adding these CheckBoxes to a StackPanel and then add the StackPanel to the grid:
StackPanel innerStack;
private void course_Loaded(object sender, RoutedEventArgs e)
{
innerStack= new StackPanel
{
Orientation=Orientation.Vertical
};
List<Course> courses = ldc.Courses.ToList();
foreach (var c in courses)
{
CheckBox cb = new CheckBox();
cb.Name = c.CourseID.ToString();
cb.Content = c.CourseID.ToString();
innerStack.Children.Add(cb);
}
Grid.SetColumn(innerStack, /*Set the column of your stackPanel, default is 0*/);
Grid.SetRow(innerStack, /*Set the row of your stackPanel, default is 0*/);
Grid.SetColumnSpan(innerStack, /*Set the columnSpan of your stackPanel, default is 1*/);
Grid.SetRowSpan(innerStack, /*Set the rowSpan of your stackPanel, default is 1*/);
Grid.Children.Add(innerStack);
}
If you do not want this structure, you should add some RowDefinition to your grid and use Grid.SetRow(cb, int) method to put ComboBoxes over each other.
You're doing it wrong.
First to say you can do grid1.Children.Add(cb);
Then the real issue is that you're using a grid to display a list. There's a very nice ListView for that in WPF with completely style-able rows that can include checkboxes and pretty much everything else you can imagine.
I don't know what your data looks like so I couldn't expand much on the ListView but something like
<ListView ItemsSource="{Binding Courses}">
<ListView.View>
<GridView>
<GridViewColumn Width="120">
<GridViewColumnHeader>
<TextBlock Text="Course Name"/>
</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ...UFigureThisOut}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="120">
<GridViewColumnHeader>
<TextBlock Text="Take That"/>
</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding ...UFigureThisOutToo}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>