First I should add that I am new to Windows Phone development, so go easy on me :-)
I want to bind a ListBox to an ObservableCollection < LinkElement > where each LinkElement is represented by a UserControl called Tile. So far the code works, I get as many Tile:s as I have LinkElement:s in my ObservableCollection (I have simplified the code below a little, there is just one LinkElement right now).
XAML:
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="10,10,10,10">
<ScrollViewer Name="linkScrollViewer">
<ListBox Name="linkList" Margin="26,0,26,0" Height="380" >
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:Tile>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu Name="ContextMenu" >
<toolkit:MenuItem Name="Edit" Header="Edit" Click="EditItem_Click"/>
<toolkit:MenuItem Name="Delete" Header="Delete" Click="DeleteItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</Controls:Tile>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
I set the binding source through code :
linkList.ItemsSource = LinkProjection.List;
And the collection I bind against (that right now doesn't do much):
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SoftTeam.SoftLink
{
public class LinkProjection
{
private Links _links;
public ObservableCollection<LinkElement> List = new ObservableCollection<LinkElement>();
public LinkProjection(Links links)
{
_links = links;
}
public void Refresh()
{
List.Clear();
var element = new LinkElement();
element.Name = "Button1";
element.Header = "Media";
element.Height = 200;
element.Width = 480;
element.IsLink = false;
element.Tag = null;
element.URL = "";
List.Add(element);
}
}
public class LinkElement
{
public string Name;
public string Header;
public string URL;
public double Height;
public double Width;
public bool IsLink;
public object Tag;
}
}
The problem is when I try to bind properties of the Tile control to properties of the LinkElement class, I get an System.ArgumentException "Value does not fall within the expected range.". The exception gives me no hint on where the problem is since it doesn't occur in my code.
That is, when I change
<Controls:Tile> // This works!
in the XAML to for example
<Controls:Tile TileHeader="{Binding Path=Header}"> // This crashes
or
<Controls:Tile Name="{Binding Path=Name}"> // This crashes too
the exceptions occur. It does not matter which property I bind to, it gives an Exception. Without an properties bound, the code works fine.
I guess my question is : WHY?
I finally found the answer in this article:
Binding properties of User Control in Windows Phone Application
The problem was that my UserControl was in another project/assembly and I therefor needed to create Dependency Proeperties for my control.
Related
I am writing this because I had difficulties in implementing a specific function during WPF implementation.
For data model
latitude and longitude (location in current window)
MainViewModel is
I am managing it as an observableCollection.
In xaml, the upper and longitude values for each model list were displayed,
The part that implements the button control in the form of an image to move according to the location information that is continuously updated in xaml is blocked, so I am posting this.
In addition, I want to display a line for the azimuth or each component, but I also want to implement this so that the line moves according to the changing value.
Is there a method that is usually used for these changing values or is there a method that is mainly used in practice?
In the case of Winform, I used the method of drawing a line using a Graphics object, but if anyone knows how to display it in real time by moving it in real time in C# WPF and binding the position value, I would appreciate it if you could share it.
In my previous answer I demonstrated a way to position a line based on a property in the viewmodel.
However, this only answers part of your question.
In the example below I will demonstrate how to have an ObservableCollection with positions that will be reflected on screen by showing the positions as text at the screen location corresponding with their values.
To start with, while the ObservableCollection can be used as an Itemsource, we must make sure the Items themselves are suitable to be used as viewmodels:
So, you could declare a Positionclass as follows (using 'CommunityToolkit.Mvvm'):
using CommunityToolkit.Mvvm.ComponentModel;
namespace ViewModels;
public partial class Position : ObservableObject
{
[ObservableProperty]
private double _x;
[ObservableProperty]
private double _y;
public Position(double x = 0.0, double y = 0.0)
{
X = x;
Y = y;
}
}
Now you can declare a MainViewModel containing a ObservableCollection<Position> property that will be used as an itemsource. For demonstration purposes, the values for the positions will be changed continuously based upon a DispatchTimer:
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
using System.Windows.Threading;
namespace PresentationLayer.ViewModels;
public class MainViewModel : ObservableObject
{
private DispatcherTimer _timer = new();
private Random _random = new();
// binding properties
public ObservableCollection<Position> TextPositions { get; private set; } = new();
public MainViewModel()
{
TextPositions.Add(new(10, 10));
TextPositions.Add(new(20, 100));
TextPositions.Add(new(50, 300));
_timer.Interval = TimeSpan.FromMilliseconds(100);
_timer.Tick += OntimerTick;
_timer.Start();
}
private void OntimerTick(object? sender, EventArgs e)
{
foreach (var item in TextPositions)
{
item.X = (item.X + _random.Next(30)) % 600;
item.Y = (item.Y + _random.Next(20)) % 350;
}
}
}
Finally, this MainViewModel can be used as datacontext for a WPF-Window displaying the positions in an ItemsControl using a datatemplate to construct the Textvalues to be displayed.
The positioning is handled using an ItemsControl.ItemContainerStyle attribute binding the position of the items.
Note: for this to work, you need to specify the ItemsPanel should be of type Canvas since the binding for the Location uses Canvas.Top and Canvas.Left.
The MainWindow (using a MainViewModelinstance as datacontext):
<Canvas>
<ItemsControl ItemsSource="{Binding TextPositions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Canvas.Left" Value="{Binding X}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate >
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="1" CornerRadius="2" Background="LightSkyBlue">
<WrapPanel>
<TextBlock Text="{Binding X}" />
<TextBlock Text=", "/>
<TextBlock Text="{Binding Y}" />
</WrapPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
For the positioning of controls or display-elements, you can use binding to a property in your ViewModel.
In the example below I am using a Canvas and bind the Canvas.Top attribute of a line to the LinePos property in the Viewmodel.
An alternative could be to bind the Rendertransform instead...
<Canvas>
<Line X1="0" Y1="10" X2="500" Y2="10" Stroke="Blue" StrokeThickness="3" Canvas.Top="{Binding LinePos}" />
</Canvas>
And the ViewModel:
public class MainViewModel : ObservableObject
{
private int _linePos;
private DispatcherTimer _timer = new();
// binding properties
public int LinePos
{
get => _linePos;
set => SetProperty(ref _linePos, value);
}
public MainViewModel()
{
_timer.Interval = TimeSpan.FromMilliseconds(10);
_timer.Tick += OnTimerTick;
_timer.Start();
}
private void OnTimerTick(object? sender, EventArgs e)
{
LinePos = (LinePos+1) % 500;
}
}
Faced the need to select a fragment of text in TextBlock, namely certain keywords on which the ListBox was filtered, this text block itself and containing
XAML variant, title property is not bound
<ListBox Name="ProcedureList" ItemsSource="{Binding Path=ProceduresView.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="ProcedurePanel" PreviewMouseDown="ProcedurePanel_OnPreviewMouseDown">
<DockPanel Width="{c:Binding ElementName=MainPanel, Path=Width-40}">
<!--<TextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left" />-->
<htb:HighlightTextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left">
<htb:HighlightTextBlock.HighlightRules>
<htb:HighlightRule
IgnoreCase="{Binding IgnoreCase, Source={StaticResource SourceVm}}"
HightlightedText="{Binding Path=title, Converter={StaticResource getFilter}}">
<htb:HighlightRule.Highlights>
<htb:HighlightBackgroung Brush="Yellow"/>
</htb:HighlightRule.Highlights>
</htb:HighlightRule>
</htb:HighlightTextBlock.HighlightRules>
</htb:HighlightTextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A component written by our compatriot with open source is used
Component
Description of component
The commented code is an old TexBlock with no selection
The new HighlightTextBlock component perfectly selects the text if you use a static resource, as in the example, but when I try to bind it to the current text it can not find this field :(, I'm new in WPF help figure it out
HightlightedText="{Binding Path=title, Converter={StaticResource getFilter}}"
How correctly to anchor this property to title?
DataContext structure
public ObservableCollection<Procedure> Procedures { set; get; }
public CollectionViewSource ProceduresView { set; get; } = new CollectionViewSource();
....
Procedures = new ObservableCollection<Procedure>();
ProceduresView.Filter += Procedures_Filter;
ProceduresView.Source = Procedures;
....
public class Procedure : ObservableObject
{
....
public String title { get; set; }
....
}
....
// Simple filtering
void Procedures_Filter(object sender, FilterEventArgs e)
{
Procedure procedure = (Procedure) e.Item;
Boolean flag = false;
if (!string.IsNullOrEmpty(filter))
{
Setting.Filter sfilter = new Setting.Filter();
sfilter.type = "искать везде";
sfilter.text = filter;
ObservableCollection<Setting.Filter> arr = new ObservableCollection<Setting.Filter>();
arr.Add(sfilter);
if (Utils.AssignedProcedureFromFilter(procedure, arr)) flag = true;
}
else flag = true;
e.Accepted = flag;
}
Video with problem description
Simplified project emitting my functional
On the Russian-speaking forum they explained to me that:
Your case, in fact, is more serious. DataContext you, apparently, the
right one. But your Binding expression is inside the HighlightRules
property setter, which is not part of the visual tree (because it is
not available as a Child element of your control). And elements that
are not inside the visual tree, participate in bindings are only
limited: they do not inherit DataContext, nor access by name through
ElementName. As a solution, bind to an element via x: Reference. In my
(heavily cut) test case, HightlightedText = "{Binding Path =
DataContext.title, Source = {x: Reference MainText}} is triggered."
But, if directly replaced by this, a strange error works: 'Can not
call MarkupExtension. ProvideValue because of a cyclic dependency. The
properties inside the MarkupExtension can not reference objects that
reference the MarkupExtension result.
The workaround for the error was found here: you need to put your element in resources. We get this:
XAML, modified according to the recommendations
<ListBox Name="ProcedureList" ItemsSource="{Binding Path=ProceduresView.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="ProcedurePanel" PreviewMouseDown="ProcedurePanel_OnPreviewMouseDown">
<DockPanel Width="{c:Binding ElementName=MainPanel, Path=Width-40}">
<!--<TextBlock Name="MainText" TextWrapping="Wrap" FontSize="16" Text="{Binding Path=title}" HorizontalAlignment="Left" />-->
<htb:HighlightTextBlock Name="MainText" TextWrapping="Wrap" FontSize="16"
Text="{Binding Path=title}" HorizontalAlignment="Left">
<htb:HighlightTextBlock.Resources>
<htb:HighlightRule x:Key="HR"
IgnoreCase="{Binding IgnoreCase, Source={StaticResource SourceVm}}"
HightlightedText="{Binding Path=DataContext.title, Source={x:Reference MainText}, Converter={StaticResource getFilter}}">
<htb:HighlightRule.Highlights>
<htb:HighlightBackgroung Brush="Yellow"/>
</htb:HighlightRule.Highlights>
</htb:HighlightRule>
</htb:HighlightTextBlock.Resources>
<htb:HighlightTextBlock.HighlightRules>
<htb:HighlightRulesCollection>
<StaticResource ResourceKey="HR"/>
</htb:HighlightRulesCollection>
</htb:HighlightTextBlock.HighlightRules>
</htb:HighlightTextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I was given advice on the restructuring of XAML, through resources, this partially solved the problem (I successfully got the title text in the converter), but the element ceased to perform its functions (allocation) During the discussion, it was suggested that the component itself should be finalized
#iRumba: In theory, the whole trick should not be necessary if you put
the HighlighRule collection (also) in a visual tree. Then the
DataContext will be automatically inherited and on idea the binding
through ElementName too will work.
#iRumba: I do not remember exactly. It seems, it is necessary to
specify to add all HighlightRule as LogicalChildren (for this purpose
on idea it is necessary to redefine protected internal override
IEnumerator LogicalChildren). This is a complicated, advanced
technique, yes.
Sorry for Google Translator
Found a solution
public class SearchHightlightTextBlock : TextBlock
{
public SearchHightlightTextBlock() : base() { }
public String SearchText
{
get { return (String)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
private static void OnDataChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
TextBlock tb = (TextBlock)source;
if (tb.Text.Length == 0)
return;
string textUpper = tb.Text.ToUpper();
String toFind = ((String)e.NewValue).ToUpper();
int firstIndex = textUpper.IndexOf(toFind);
String firstStr = "";
String foundStr = "";
if (firstIndex != -1)
{
firstStr = tb.Text.Substring(0, firstIndex);
foundStr = tb.Text.Substring(firstIndex, toFind.Length);
}
String endStr = tb.Text.Substring(firstIndex + toFind.Length,
tb.Text.Length - (firstIndex + toFind.Length));
tb.Inlines.Clear();
tb.FontSize = 16;
var run = new Run();
run.Text = firstStr;
tb.Inlines.Add(run);
run = new Run();
run.Background = Brushes.Yellow;
run.Text = foundStr;
tb.Inlines.Add(run);
run = new Run();
run.Text = endStr;
tb.Inlines.Add(run);
}
public static readonly DependencyProperty SearchTextProperty =
DependencyProperty.Register("SearchText",
typeof(String),
typeof(SearchHightlightTextBlock),
new FrameworkPropertyMetadata(null, OnDataChanged));
}
Use
<parser:SearchHightlightTextBlock SearchText="{Binding Path=title, Converter={StaticResource getFilter}}" Text="{Binding title}"/>
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
I am trying to display data from my SQL Server Compact 3.5. On my OnNavigateTo function, I have stated the codes but I am not sure why it is not able to load it. I am using Pivot App, Is it possible to use that to display my data? If yes, what have I done wrong. In the header=today is where I am displaying my data. Thanks.
Below are my codes
MainPage.xaml
<!--Pivot Control-->
<phone:Pivot Title="DAILY ROUTINE">
<!--Pivot item one-->
<phone:PivotItem Header="activity">
<!--Double line list with text wrapping-->
<phone:LongListSelector x:Name="MLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="LongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PivotItem>
<!--Pivot item two-->
<phone:PivotItem Header="today">
<!--Double line list with text wrapping-->
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="LongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding Id}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PivotItem>
</phone:Pivot>
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using MyPhoneApp1.Resources;
namespace MyPhoneApp1
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
// Load data for the ViewModel Items
protected override void OnNavigatedTo(NavigationEventArgs e)
{
/*
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
* */
using (ToDoListContext c = new ToDoListContext(ToDoListContext.ConnectionString))
{
c.CreateIfNotExists();
c.LogDebug = true;
MainLongListSelector.ItemsSource = c.ToDoLists.ToList();
}
}
private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var si = MLongListSelector.SelectedItem as MyPhoneApp1.ViewModels.ItemViewModel;
if (MLongListSelector.SelectedItem == null)
return;
if (si.LineOne.Equals("+ To Do List"))
NavigationService.Navigate(new Uri("/todolistPage.xaml", UriKind.Relative));
else if (si.LineOne.Equals("+ Reminder"))
NavigationService.Navigate(new Uri("/reminderPage.xaml", UriKind.Relative));
// Reset selected item to null (no selection)
MLongListSelector.SelectedItem = null;
}
}
}
I have debugged it and below is the SQL Statements
SELECT [t0].[Id], [t0].[Title]
FROM [ToDoLists] AS [t0]
I wouldn't suggest you directly set the ItemsSource property as you've already established a Binding in the XAML:
<phone:LongListSelector x:Name="MLongListSelector"
ItemsSource="{Binding Items}" >
Since the Binding Path is set to Items, changing the data of the list stored in Items will cause the UI to update automatically.
// get the list ...
var list = c.ToDoLists.ToList();
Debug.Assert(list != null);
// clear any existing items, which will in turn remove all items from the UI
App.ViewModel.Items.Clear();
// for each item in the list, add it to the existing bound Items list
foreach(var item in list) {
// you may need to transform the data here
// The item must be the right type ...
App.ViewModel.Items.Add(item);
}
As it looks like you're using the WP8 template, the ToDoLists property needs to return an enumerable list of ItemViewModels or the call to Add will fail. You could create new instances of an ItemViewModel if the types don't match (for example):
var itemViewModel = new ItemViewModel() {
LineOne = item.Text,
LineTwo = item.Description
};
App.ViewModel.Items.Add(itemViewModel);
The above code assumes then that a todo list item might look like this:
public class TodoItem {
public string Text { get; set; }
public string Description { get; set; }
}
I think this might be the problem you are binding the MainLongListSelector twice
in Xaml
ItemsSource="{Binding Items}"
and in c#
MainLongListSelector.ItemsSource = c.ToDoLists.ToList();
Looks like you need to remove the xaml binding
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]));
}
}
}