I have an app for displaying songs on an overhead projector. The way it is currently written is with a stack panel of text boxes with each line of each verse displaying on each. There is also an option to display chords above each line.
The problem I am facing is that people often want to make the font smaller or larger for both the song text and the chord text. Every time this changes, the songs have to be edited to line up the chords again. The alignment of text would also effect the position of the chords.
I am looking for a more robust way to line up a chord with a section of text and keep it in the same location relative to it's word. I have thought of using a canvas, but wouldn't know how to line the text up with the correct word.
I am kind of at a loss as to what would work best for this and would appreciate any advice.
I figured it'd be a good idea to keep the Chord and the Txt Char it references together. A StackPanel holding 2 TextBlocks would do. From there it's just thinking outwards.
Each line could be an ItemsControl stacking these StackPanels horizontally.
A regular ListBox can then hold these lines.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
namespace Karaoke
{
public class ChordData
{
public ChordData(string chord, int position)
{
Chord = chord;
Position = position;
}
#region Chord Property
private String _chord;
public String Chord
{
get { return _chord; }
set { if (value != _chord) _chord = value; }
}
#endregion Chord Property
#region Position Property
private int _position;
public int Position
{
get { return _position; }
set { if (value != _position) _position = value; }
}
#endregion Position Property
}
public class KaraokeChar
{
public KaraokeChar(char txt)
{
Txt = txt;
Chord = "";
}
#region Txt Property
private Char _txt;
public Char Txt
{
get { return _txt; }
set { if (value != _txt) _txt = value; }
}
#endregion Txt Property
#region Chord Property
private String _chord;
public String Chord
{
get { return _chord; }
set { if (value != _chord) _chord = value; }
}
#endregion Chord Property
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propName = null)
{
// C#6.O
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class ItemViewModel : ViewModelBase
{
public ItemViewModel(string txt, List<ChordData> chordList)
{
foreach (char c in txt)
{
Line.Add(new KaraokeChar(c));
}
foreach (ChordData chord in chordList)
{
Line[chord.Position].Chord = chord.Chord;
}
}
#region Line Property
private ObservableCollection<KaraokeChar> _line = new ObservableCollection<KaraokeChar>();
public ObservableCollection<KaraokeChar> Line
{
get { return _line; }
set
{
if (value != _line)
{
_line = value;
OnPropertyChanged();
}
}
}
#endregion Line Property
}
public class MainViewModel : ViewModelBase
{
#region Song Property
private ObservableCollection<ItemViewModel> _song = new ObservableCollection<ItemViewModel>();
public ObservableCollection<ItemViewModel> Song
{
get { return _song; }
set
{
if (value != _song)
{
_song = value;
OnPropertyChanged();
}
}
}
#endregion Song Property
#region TextFont Property
private int _textFont;
public int TextFont
{
get { return _textFont; }
set
{
if (value != _textFont)
{
_textFont = value;
OnPropertyChanged();
}
}
}
#endregion TextFont Property
#region ChordFont Property
private int _chordFont;
public int ChordFont
{
get { return _chordFont; }
set
{
if (value != _chordFont)
{
_chordFont = value;
OnPropertyChanged();
}
}
}
#endregion ChordFont Property
}
}
MainWindow.xaml.cs
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Karaoke
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel.TextFont = 25;
ViewModel.ChordFont = 20;
SongData();
}
private void SongData()
{
ObservableCollection<ItemViewModel> Song = new ObservableCollection<ItemViewModel>();
Song.Add(new ItemViewModel("The dog and the cat",
new List<ChordData>() { new ChordData("D", 0) }));
Song.Add(new ItemViewModel("They take up the middle",
new List<ChordData>()));
Song.Add(new ItemViewModel("Where the honey bee hums",
new List<ChordData>() { new ChordData("A", 8) }));
Song.Add(new ItemViewModel("And Coyote howls",
new List<ChordData>() { new ChordData("A", 2), new ChordData("D", 9) }));
ViewModel.Song = Song;
}
// C#6.O
// public MainViewModel ViewModel => (MainViewModel)DataContext;
public MainViewModel ViewModel
{
get { return (MainViewModel)DataContext; }
}
}
}
MainWindow.xaml
<Window x:Class="Karaoke.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Karaoke"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<StackPanel Background="Black">
<Label Foreground="Yellow" FontSize="{Binding TextFont}" HorizontalAlignment="Center">All God's Critters</Label>
<ListBox ItemsSource="{Binding Song}"
Background="Transparent"
BorderBrush="Transparent"
Margin="40,0">
<ListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Line}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Chord}" FontSize="{Binding DataContext.ChordFont, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Foreground="Purple"/>
<TextBlock Text="{Binding Txt}" FontSize="{Binding DataContext.TextFont, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Foreground="White"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Changing fontsizes as simple as changing a ViewModel prop:
The way I would do this, would be to have each word that has a chorus, to be in a two line textblock (use "Run" and "Linebreak" inside TextBlock content). This would mean that you will have to split your lines into lines with chorus and lines without.
If a line has chorus, than you have to have it split into textblocks without chorus, followed by textblocks with chorus, and then textblocks without chorus. (you could use a stackpanel with Horizontanl orientation to have it all on the same line)
It not a simple matter, but it could make it so that you have what you want.
Related
I've tried solving this myself, looking at several possible solutions here on Stack Overflow, but alas I've been unable to solve this issue.
TL;DR version:
The problem:
A listBox using databinding to show a list of RPG characters, which have a nested property for their attributes. I can't get the attributes to show due to the limitations with nested properties and databindings.
The code below is related to the issue.
I have a listBox that has a databinding that controls what is shown in the list. The databinding uses ObservableCollection for the list of objects that the list contains. All this works fine, but is related to the issue at hand.
The listBox databinding has several nested properties in each element, that I want to display and change in the form, yet I cannot get nested databinding to work correctly.
This is the listBox XAML:
<ListBox x:Name="listCharacters" Margin="2,0" ItemsSource="{Binding}" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Hidden" SelectionChanged="listCharacters_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Character}" x:Name="Symchar">
<Grid Width="125" HorizontalAlignment="Left" Background="{x:Null}">
<Grid.RowDefinitions>
<RowDefinition Height="18"/>
<RowDefinition Height="12"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Image Panel.ZIndex="5" HorizontalAlignment="Right" VerticalAlignment="Top" Height="16" Width="16" Margin="0,2,0,0" Source="Resources/1454889983_cross.png" MouseUp="DeleteCharacter" />
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" FontSize="13.333" Grid.Row="0" TextTrimming="CharacterEllipsis" Padding="0,0,16,0" />
<TextBlock Text="{Binding RealRace.Label, UpdateSourceTrigger=PropertyChanged}" FontSize="9.333" Grid.Row="1" FontStyle="Italic" />
<TextBlock FontSize="9.333" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top">
<Run Text="{Binding RealClass.Archetype.Label, UpdateSourceTrigger=PropertyChanged}"/>
<Run Text=" - "/>
<Run Text="{Binding RealClass.Label, UpdateSourceTrigger=PropertyChanged}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And setting the listBox ItemSource:
this.listCharacters.ItemsSource = CharacterList;
This is the character class, I removed unrelated code (XML serialization attributes etc.)
public class Character : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
this.NotifyPropertyChanged("Name");
}
}
private string _player;
public string Player
{
get { return _player; }
set
{
_player = value;
this.NotifyPropertyChanged("Player");
}
}
private string _race;
public string Race
{
get { return _race; }
set
{
_race = value;
this.NotifyPropertyChanged("Race");
}
}
private Race _realRace;
public Race RealRace
{
get { return _realRace; }
set
{
_realRace = value;
Race = value.Id;
this.NotifyPropertyChanged("RealRace");
}
}
private string _gender;
public string Gender
{
get { return _gender; }
set
{
_gender = value;
this.NotifyPropertyChanged("Gender");
}
}
private Attributes _attributes;
public Attributes Attributes
{
get { return _attributes; }
set
{
_attributes = value;
this.NotifyPropertyChanged("Attributes");
}
}
private string _class;
public string Class
{
get { return _class; }
set
{
_class = value;
this.NotifyPropertyChanged("Class");
}
}
private Class _realClass;
public Class RealClass
{
get { return _realClass; }
set
{
_realClass = value;
Class = value.Id;
this.NotifyPropertyChanged("RealClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
To keep it simple, the property that I've been testing with, is the 'Attributes' property, this is the code for it:
public class Attributes : INotifyPropertyChanged
{
private int _accurate;
public int Accurate
{
get { return _accurate; }
set
{
_accurate = value;
this.NotifyPropertyChanged("Accurate");
}
}
private int _cunning;
public int Cunning
{
get { return _cunning; }
set
{
_cunning = value;
this.NotifyPropertyChanged("Cunning");
}
}
private int _discreet;
public int Discreet
{
get { return _discreet; }
set
{
_discreet = value;
this.NotifyPropertyChanged("Discreet");
}
}
private int _persuasive;
public int Persuasive
{
get { return _persuasive; }
set
{
_persuasive = value;
this.NotifyPropertyChanged("Persuasive");
}
}
private int _quick;
public int Quick
{
get { return _quick; }
set
{
_quick = value;
this.NotifyPropertyChanged("Quick");
}
}
private int _resolute;
public int Resolute
{
get { return _resolute; }
set
{
_resolute = value;
this.NotifyPropertyChanged("Resolute");
}
}
private int _strong;
public int Strong
{
get { return _strong; }
set
{
_strong = value;
this.NotifyPropertyChanged("Strong");
}
}
private int _vigilant;
public int Vigilant
{
get { return _vigilant; }
set
{
_vigilant = value;
this.NotifyPropertyChanged("Vigilant");
}
}
private int _toughness;
public int Toughness
{
get { return _toughness; }
set
{
_toughness = value;
this.NotifyPropertyChanged("Toughness");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I want to display each individual attribute in a field when a character in the listBox is selected, this works fine with properties directly in the character class, but due to the limitations on nested properties and databindings, I haven't been able to get it to work with the 'Attributes' properties values.
XAML for one of the attribute input fields:
<TextBox x:Name="attr_Accurate" DataContext="{Binding Path=(local:Character.Attributes), XPath=SelectedItem, ElementName=listCharacters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Path=(local:Accurate)}" PreviewTextInput="NumericInput"/>
The UpdateSourceTrigger is simply a method to only allow integers to be input in the field:
private void NumericInput(object sender, TextCompositionEventArgs e)
{
if (!char.IsDigit(e.Text, e.Text.Length - 1))
{
e.Handled = true;
}
}
If anyone could help me get the values within the selected character's attributes to show up via databindings, I would greatly appreciate it.
Use binding as following:
To Show Accurate Property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Accurate), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
To Show Cunning property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Cunning), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
and so one.
(I'm not sure if you want binding to be two way or one so please
change as your need)
I'm currently trying to make a grid consisting of Cell objects in WPF. I need to bind the cells to objects, which needs to be in a 2D array. - And i need it to be big, scalable and to change color of cells and store data in the objects!
I have an implementation made, but it seems to be very slow to draw the grid! (100x100 grid takes >10 secs!)
Here is a picture of what i have already made:
I'm using databinding in XAML in an ItemsControl. Here is my XAML:
<ItemsControl x:Name="GridArea" ItemsSource="{Binding Cellz}" Grid.Column="1" BorderBrush="Black" BorderThickness="0.1">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Cell}">
<Border BorderBrush="Black" BorderThickness="0.1">
<Grid Background="{Binding CellColor}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove" >
<ei:CallMethodAction TargetObject="{Binding}" MethodName="MouseHoveredOver"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding Rows}" Columns="{Binding Columns}" MouseDown="WrapPanelMouseDown" MouseUp="WrapPanelMouseUp" MouseLeave="WrapPanelMouseLeave" >
<!--<UniformGrid.Background>
<ImageBrush/>
</UniformGrid.Background>-->
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
In my codebehind i instantiate a object of a class called Grid, which makes a 2D array (and a list, which is what im binding to) with objects of the Cell class. After some checking with a stopwatch, i can see that this is not taking the time. Its the actual binding and drawing the grids, so i guess my optimization should happen in my XAML, if any optimization is available.
But to provide everything, here is my code behind and the grid class and the cell class:
public MainWindow()
{
InitializeComponent();
NewGrid = new Grid(75, 75);
DataContext = NewGrid;
}
public class Grid
{
public int Columns { get; set; }
public int Rows { get; set; }
public ObservableCollection<Cell> Cellz {get;set;}
public Cell[,] CellArray { get; set; }
public Grid(int columns, int rows)
{
Columns = columns;
Rows = rows;
Cellz = new ObservableCollection<Cell>();
CellArray = new Cell[Rows,Columns];
InitializeGrid();
}
public void InitializeGrid()
{
Color col = Colors.Transparent;
SolidColorBrush Trans = new SolidColorBrush(col);
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var brandNewCell = new Cell(i, j) { CellColor = Trans};
Cellz.Add(brandNewCell);
CellArray[i, j] = brandNewCell;
}
}
}
public class Cell : INotifyPropertyChanged
{
public int x, y; // x,y location
public Boolean IsWall { get; set; }
private SolidColorBrush _cellcolor;
public SolidColorBrush CellColor
{
get { return _cellcolor; }
set
{
_cellcolor = value;
OnPropertyChanged();
}
}
public Cell(int tempX, int tempY)
{
x = tempX;
y = tempY;
}
public bool IsWalkable(Object unused)
{
return !IsWall;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(
[CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
I like the quite simple implementation with binding, however the loadtime is really unacceptable - Any advice would be greatly appreciated!
Well, i recreated your example, with some changes. I mainly got rid of the bindings on the DataContext, and created a viewmodel specifically for your use case, that gets bound directly to the itemscontrol.
The speed of drawing is definitely under the 10 seconds, but I thought i gave you as much of the relevant code as possible so you could compare the solutions...
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using TestSO.model;
namespace TestSO.viewmodel
{
public class ScreenViewModel : INotifyPropertyChanged, IDisposable
{
public event PropertyChangedEventHandler PropertyChanged;
private IList<Cell> cells;
public IList<Cell> Cells
{
get
{
return cells;
}
set
{
if (object.Equals(cells, value))
{
return;
}
UnregisterSource(cells);
cells = value;
RegisterSource(cells);
RaisePropertyChanged("Cells");
}
}
private int rows;
public int Rows
{
get
{
return rows;
}
set
{
if (rows == value)
{
return;
}
rows = value;
RaisePropertyChanged("Rows");
}
}
private int columns;
public int Columns
{
get
{
return columns;
}
set
{
if (columns == value)
{
return;
}
columns = value;
RaisePropertyChanged("Columns");
}
}
private Cell[,] array;
public Cell[,] Array
{
get
{
return array;
}
protected set
{
array = value;
}
}
protected void RaisePropertyChanged(string propertyName)
{
var local = PropertyChanged;
if (local != null)
{
App.Current.Dispatcher.BeginInvoke(local, this, new PropertyChangedEventArgs(propertyName));
}
}
protected void RegisterSource(IList<Cell> collection)
{
if (collection == null)
{
return;
}
var colc = collection as INotifyCollectionChanged;
if (colc != null)
{
colc.CollectionChanged += OnCellCollectionChanged;
}
OnCellCollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, collection, null));
}
protected virtual void OnCellCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
Array = null;
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
var cell = item as Cell;
if (cell == null)
{
continue;
}
if (Array == null)
{
continue;
}
Array[cell.X, cell.Y] = null;
}
}
if (e.NewItems != null)
{
if (Array == null)
{
Array = new Cell[Rows, Columns];
}
foreach (var item in e.NewItems)
{
var cell = item as Cell;
if (cell == null)
{
continue;
}
if (Array == null)
{
continue;
}
Array[cell.X, cell.Y] = cell;
}
}
}
protected void UnregisterSource(IList<Cell> collection)
{
if (collection == null)
{
return;
}
var colc = collection as INotifyCollectionChanged;
if (colc != null)
{
colc.CollectionChanged -= OnCellCollectionChanged;
}
OnCellCollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public ScreenViewModel()
{
}
public ScreenViewModel(int row, int col)
: this()
{
this.Rows = row;
this.Columns = col;
}
bool isDisposed = false;
private void Dispose(bool disposing)
{
if (disposing)
{
if (isDisposed)
{
return;
}
isDisposed = true;
Cells = null;
}
}
public void Dispose()
{
Dispose(true);
}
}
}
And i created as an extra, a controller, who is the owner of the ObservableCollection, main purpose would be to not do any changes on the viewModel, but rather change the collection inside the controller (or add add, remove, clear methods to it), and let the event chain do the work for me, keeping the 2Dimensional Array up to date in the ScreenViewModel
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TestSO.model;
namespace TestSO.controller
{
public class GenericController<T>
{
private readonly IList<T> collection = new ObservableCollection<T>();
public IList<T> Collection
{
get
{
return collection;
}
}
public GenericController()
{
}
}
public class CellGridController : GenericController<Cell>
{
public CellGridController()
{
}
}
}
And your cell class, i slightly adjusted it with only raising the change event in case there was actually a change
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
namespace TestSO.model
{
public class Cell : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var local = PropertyChanged;
if (local != null)
{
Application.Current.Dispatcher.BeginInvoke(local, this, new PropertyChangedEventArgs(propertyName));
}
}
private int x;
public int X
{
get
{
return x;
}
set
{
if (x == value)
{
return;
}
x = value;
RaisePropertyChanged("X");
}
}
private int y;
public int Y
{
get
{
return y;
}
set
{
if (y == value)
{
return;
}
y = value;
RaisePropertyChanged("Y");
}
}
private bool isWall;
public bool IsWall
{
get
{
return isWall;
}
set
{
if (isWall == value)
{
return;
}
isWall = value;
RaisePropertyChanged("IsWall");
}
}
private SolidColorBrush _cellColor;
public SolidColorBrush CellColor
{
get
{
// either return the _cellColor, or say that it is transparent
return _cellColor ?? Brushes.Transparent;
}
set
{
if (SolidColorBrush.Equals(_cellColor, value))
{
return;
}
_cellColor = value;
RaisePropertyChanged("CellColor");
}
}
public Cell()
{
}
public Cell(int x, int y)
: this()
{
this.X = x;
this.Y = y;
}
}
}
Then I changed the xaml a bit (though didn't take over the interaction points), by creating the resources for the ScreenViewModel, the controller, and a DataTemplate, this Template, is then also DataTemplate directly added to the ItemsControl over the ItemTemplate, instead of using the DataTemplate functionality (didn't see that as a requirement above?)
<Window x:Class="TestSO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:TestSO.model"
xmlns:viewmodel="clr-namespace:TestSO.viewmodel"
xmlns:controller="clr-namespace:TestSO.controller"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<controller:CellGridController x:Key="CellController" />
<viewmodel:ScreenViewModel x:Key="GridViewModel" Rows="75" Columns="75" />
<DataTemplate x:Key="CellTemplate">
<Border BorderBrush="Black" BorderThickness="0.5">
<Grid Background="{Binding CellColor}">
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Cells,Source={StaticResource GridViewModel}}" BorderBrush="Black" BorderThickness="0.1" ItemTemplate="{StaticResource CellTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
Rows="{Binding Rows,Source={StaticResource GridViewModel}}"
Columns="{Binding Columns,Source={StaticResource GridViewModel}}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Window>
And inside main.cs page, i loaded I linked the Collection of the controller with the ScreenViewModel.Cells property, and loaded some template data. Just pretty basic mock data (you could also attach the screenmodel to the DataContext and define the controller somewhere else, and change the bindings in the xaml to go back to the DataContext, however over the resources, you can also get to the already created instances (after initializeComponent)
protected ScreenViewModel ScreenViewModel
{
get
{
return this.Resources["GridViewModel"] as ScreenViewModel;
}
}
protected CellGridController Controller
{
get
{
return this.Resources["CellController"] as CellGridController;
}
}
protected void Load()
{
var controller = Controller;
controller.Collection.Clear();
string[] rows = colorToCellSource.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
string row;
for (int x = 0; x < rows.Length; x++)
{
int length = rows[x].Length;
ScreenViewModel.Rows = rows.Length;
ScreenViewModel.Columns = length;
row = rows[x];
for (int y = 0; y < length; y++)
{
Cell cell = new Cell(x, y);
cell.CellColor = row[y] == '0' ? Brushes.Transparent : Brushes.Blue;
controller.Collection.Add(cell);
}
}
}
public MainWindow()
{
InitializeComponent();
if (Controller != null && ScreenViewModel != null)
{
ScreenViewModel.Cells = Controller.Collection;
Load();
}
}
The screen takes gets redrawn here under 1 second, resizing and maximizing takes a small delay, but i guess that can be expected... (My test template was 105x107)
It sounds like the delay is being caused by resolving the template for and rendering so many items.
Some suggestions to improve it :
Don't store empty cells. Only store cells with "data" in them.
Bind using a static ItemTemplate instead of resolving the type dynamically
Try simplifing your ItemTemplate
For the first point, I would try using two layers here instead of just one: one layer to draw the grid, and another sitting on top of it to draw objects at specific locations in the Grid.
XAML would look something like this
<Grid>
<UniformGrid Rows="{Binding RowCount}" Columns="{Binding ColumnCount}" />
<ItemsControl ItemsSource="{Binding Cells}" .... />
</Grid>
Where the ItemsControl uses a Canvas for the ItemContainerTemplate, and binds the Canvas.Top and Canvas.Left to the X,Y cell data in the ItemContainerStyle, probably using some kind of Converter to multiply the X,Y value by the grid cell size.
To get GridLines to show, you can either settle with ShowGridLines=True to get the faint dotted lines, or use a customized Grid like this one that overwrites OnRender to draw grid lines. This would also help reduce the number of objects in your ItemTemplate for the 3rd point, as you don't need the <Border> object now.
I'm also not entirely sure how you want to interact with your Grid, but it may help with initial load times to put all the mouse handlers on the background Grid itself instead of on each individual item too, and simply calculate the item below the mouse by the X,Y position. You'd probably have to ensure the IsHitTestVisible="False" is set in the ItemContainerStyle for this.
For the second point (and as Icepickle pointed out), using a static template instead of a dynamic one based on type of object will definitely help.
So instead of using an implicit template, give your template an x:Key property and bind it using a static binding
<DataTemplate x:Key="CellTemplate" DataType="{x:Type local:Cell}">
<Border BorderBrush="Black" BorderThickness="0.1">
<Grid Background="{Binding CellColor}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove" >
<ei:CallMethodAction TargetObject="{Binding}" MethodName="MouseHoveredOver"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</Border>
</DataTemplate>
<ItemsControl ItemTemplate="{StaticResource CellTemplate}" ... />
My WPF Application code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . I want to Name Textbox defined in this ItemControl and to use this in code. I named it as textEdit1 and used it in code but code generated error that textEdit1 doesn't exist. Can anyone solve my problem? Here Code is:
XAML File:
<dxlc:ScrollBox>
<ItemsControl Name="lstPanels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical">
<Grid>
<dxe:TextEdit Height="165" Text="{Binding Text,
Mode=TwoWay}" x:Name="textEdit1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</dxlc:ScrollBox>
.CS FILE
public partial class Window1 : Window
{
string valuu;
public Window1()
{
InitializeComponent();
addPanel("Header1");
addPanel("Header2");
addPanel("Header3");
lstPanels.ItemsSource = panels;
}
public ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
public void addPanel(string buttonId)
{
MyPanel p = new MyPanel { Id = buttonId};
panels.Add(p);
functionb(p);
}
public void functionb(MyPanel obj)
{
valuu = obj.Text;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
foreach (var f in panels.ToList())
{
MessageBox.Show( f.Id + " *** " + f.Text);
}
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I see that you are using some 3rd party libraries for your TextBox and ScrollBox. If you provide me with the names of the libraries, I could have a look at them as the functionality might be different from what WPF has out-of-the-box.
As for now you have 3 options (I am giving examples for standard TextBox and ItemsControl):
I) You do not have to access the textbox at all.
An easy way around it is described here: StackOverflow post
II) Handling events and references to TextBoxes in the code behind
Add a Loaded event to your TextBox:
<TextBox x:Name="txtText" Width="300" Height="100" Loaded="txtText_Loaded" />
Add a field to your MyPanel class to hold a reference to a TextBox:
public class MyPanel
{
public string Text { get; set; }
public TextBox TextBox { get; set; }
/* the rest ... */
}
Add a counter to your window, next to a list with panels:
protected ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
private int counter = 0;
Handle the Load event of the TextBox:
private void txtText_Loaded(object sender, RoutedEventArgs e)
{
panels[counter].TextBox = (TextBox)sender;
counter++;
}
If you want to access a particular TextBox, do it this way:
MessageBox.Show(panels[i].TextBox.Text);
III) Add additional bindings for FontSize:
Add a FontSize property to your MyPanel class:
private double _fontSize = 10;
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
Bind just added property to the TextBox in your ItemsControl:
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}"
FontSize="{Binding FontSize, Mode=OneWay}" />
Add a slider to the template and bind it to the same property:
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
This way if you change the value on a slider, it will change the value in your MyPanel object bound to the panel. This in turn will change the font size of the textbox.
My whole code I tested it on looks like that:
<ItemsControl x:Name="lstItems" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And code behind:
public partial class MainWindow : Window
{
protected ObservableCollection<MyPanel> texts = new ObservableCollection<MyPanel>();
public MainWindow()
{
InitializeComponent();
texts.Add(new MyPanel() { Text = "Test 1" });
texts.Add(new MyPanel() { Text = "Test 2" });
lstItems.ItemsSource = texts;
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
private double _fontSize = 10;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I personally would go with the last solution.
But again, let me know what libraries you are using, and I will have look at them when I have some time. Good luck.
textEdit1 is part of a template that will be instantiated multiple times, so there will be multiple instances of textEdit1. It wouldn't make sense to generate a field for textEdit1 in the class, because it could only refer to one instance the TextEdit control...
My code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . Each Panel has it's Textbox , Slider and Combobox.
Each Panel's Slider and Combobox is playing with TextBox.Text Like:
Slider to increase Textbox.Text Font Size.
Combobox to Select alignment of TextBox.Text.
I want to replace Combobox with Button with Content Left. So as i click on Button it's Content must change to Right and similarly from Right to Left., to make changes to Alignment.
Can anyone solve my problem? Here Code is:
XAML FILE:
<ItemsControl x:Name="lstItemsClassM" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<ComboBox x:Name="cboOccupation" IsEditable="False" HorizontalAlignment="Left"
Text="{Binding Path=Alignment, Mode=TwoWay}"
Margin="4" Width="140">
<ComboBoxItem>Right</ComboBoxItem>
<ComboBoxItem>Left</ComboBoxItem>
</ComboBox>
<Button Content="{Binding Alignment, Mode=TwoWay}" Click="Button_Click" Tag="{Binding PKId}" SourceUpdated="Button_SourceUpdated" />
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" TextAlignment="{Binding Alignment, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
.cs File
public partial class Window2 : Window
{
protected ObservableCollection<ClassM> texts = new ObservableCollection<ClassM>();
int dv;
public Window2()
{
InitializeComponent();
dv=1;
texts.Add(new ClassM() { PKId=dv, Text = "Test 1" });
dv=2;
texts.Add(new ClassM() { PKId=dv, Text = "Test 2" });
lstItemsClassM.ItemsSource = texts;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var myValue = ((Button)sender).Tag;
foreach (var f in texts.ToList())
{
if (f.PKId.ToString() == myValue.ToString())
{
f._alignment = "Right";
MessageBox.Show(f._alignment);
}
}
}
private void Button_SourceUpdated(object sender, DataTransferEventArgs e)
{
var myValue = ((Button)sender).Tag;
foreach (var f in texts.ToList())
{
if (f.PKId.ToString() == myValue.ToString())
{
f._alignment = "Right";
MessageBox.Show(f._alignment);
}
}
}
}
public class ClassM : INotifyPropertyChanged
{
private string _id;
private int _pkid;
private string _text;
private double _fontSize = 10;
public bool _isChecked { get; set; }
public string _alignment="Left";
public int PKId
{
get { return _pkid; }
set
{
if (value != _pkid)
{
_pkid = value;
NotifyPropertyChanged();
}
}
}
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public string Alignment
{
get { return _alignment; }
set
{
if (value != _alignment)
{
_alignment = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The TextBox.TextAlignment property is of the enumeration type System.Windows.TextAlignment.
If you want to bind it without having to implement your own value converter then you want to populate the ComboBox with the actual enum values, not just a string with the same name like "Left" and "Right"...
Here's how you'd make a ComboBox represent TextAlignment:
<ComboBox>
<ComboBox.Items>
<TextAlignment>Left</TextAlignment>
<TextAlignment>Right</TextAlignment>
</ComboBox.Items>
</ComboBox>
Now you can bind the SelectedItem property of the ComboBox to the TextAlignment property of the TextBox.
Or in your case if you want to bind both to an underlying data context then you would need to change this:
private string _alignment = "Left";
To this:
private TextAlignment _alignment = TextAlignment.Left;
I'm really new to WPF so apologies in adavnced if this is an obvious question. I have a simple Checkbox in XAML as
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Simplified code behind to allow bindings and INotifyPropertyChanged is:
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
}
Selections = new ObservableCollection<CheckedListItem<Selection>>();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true));
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
I now need to add an additional TextBox associated with each Checkbox, so in XAML I have
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
<<TextBox />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm a bit stumped how to include this as part of the ObservableCollection and set it up the binding on both the CheckBox and associated TextBox? Both are being added together using Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true)); which is causing me some confusion.
EDIT: Added full code
public partial class SelectionSettingWindow : Window
{
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
public string SelectionTextField { get; set; }
}
public SelectionSettingWindow()
{
InitializeComponent();
Selections = new ObservableCollection<CheckedListItem<Selection>>();
string fg = #"Item1,true,TExtbox1text:Item2,true,TExtbox2text:Item3,false,TExtbox3text"; //Test String
string[] splitSelections = fg.Split(':');
foreach (string item in splitSelections)
{
string[] spSelectionSetting = item.Split(',');
bool bchecked = bool.Parse(spSelectionSetting[1].ToString());
string tbText = spSelectionSetting[2].ToString();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = spSelectionSetting[0].ToString(),
SelectionTextField = bText }, isChecked: bchecked));
}
DataContext = this;
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string textField;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string TextField
{
get { return textField; }
set
{
textField = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TextField"));
}
}
}
}
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Path=Item.SelectionName}" />
<TextBox Text="{Binding Item.SelectionTextField, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
replace SelectionTextField above with whatever the field is that needs to be edited using the textbox on your Selection class.
Note that I changed the <Grid> to a <StackPanel> So they wouldn't appear on top of eachother and changed the bindings to TwoWay so the changes are reflected in the model.
Make sure your Selection class implements INotifyPropertyChanged (ObservableCollection updates the UI when things get added to/removed from the collection, it doesn't know anything about notifying when it's content's properties change so they need to do that on their own)
Implementing INotifyPropertyChanged on many classes can be cumbersome. I find implementing a base class useful for this. I've got this along with an extra reflection helper for raise property changed available here and a snippet I've made available. It's silverlight but it should work fine for WPF. Using the code I've provided via download you can simply type proprpc and hit tab and visual studio will stub in a property that notifies on change. Some explanation is in one of my old blog posts here and gives credit for where I based the code and snippet from.