Calculating textbox with two selected combobox items - c#

My problem: Id like to , after two combobox variables are selected, to divide these two and set the Textbox to the result of the calculation.
The two Comboboxes: Körpergröße & Gewicht
The textbox: BMI
First of all, the code im using ( which apparently isnt working now)
private void fillTextBox(float value1, float value2)
{
BMI.Text = (value1 / value2).ToString();
}
private void Körpergröße_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
float a;
float b;
//In this way you can compare the value and if it is possible to convert into an integer.
if (float.TryParse(Körpergröße.SelectedItem.ToString(), out a) && float.TryParse(Gewicht.SelectedItem.ToString(), out b))
{
fillTextBox(a, b);
}
}
private void Gewicht_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
float a;
float b;
//In this way you can compare the value and if it is possible to convert into an integer.
if (float.TryParse(Körpergröße.SelectedItem.ToString(), out a) && float.TryParse(Gewicht.SelectedItem.ToString(), out b))
{
fillTextBox(a, b);
}
}
The default values of the two comboboxes are strings.. ("Bitte auswählen")
Pictures how it looks like now. After two int values are selected, the result of the calculation should appear in the BMI Textbox, but its still blank. it looks like the ToString() Methods do not save into a, and neither into b..therefore the values cant be used in the fillTextBox method
It would be nice if someone could answer me with a code with some sidenotes, in order for me to understand..
Thanks in advance!

Here's an example of how you would write a trivial BMI calculator in WPF. This is the way XAML is intended to be used: All of the logic is in the View Model class, BMIViewModel. The View (XAML file) wraps a UI around that logic. The codebehind file is only used if you need to provide some special logic unique to the view itself. Very often it is not used at all. Here, it's not used.
This is very different from what you may be used to and it's a steep learning curve in a lot of ways, but I've learned to like it very much. If you break up program logic into sensible chunks in various View Models, you can present those View Models in the UI in various differing ways. You get tremendous freedom and power. Once you've got a debugged and tested View Model, you can write new UI for it without touching the program logic at all.
If you study and understand this code, you will have a rudimentary but solid basis to start learning XAML. The bindings are important, and OnPropertyChanged is very important: That notification is how the view model lets the bindings know that a value has changed.
I don't like writing this much code for a beginner, but your code (which is not your code -- it's entirely copied from bad answers to previous iterations of this question), is unusable.
XAML:
<Window
x:Class="TestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApplication"
Title="MainWindow" Height="350" Width="525"
>
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style x:Key="FieldLabel" TargetType="Label">
<Setter Property="Width" Value="120" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="Width" Value="140" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Width" Value="140" />
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="Height" Style="{StaticResource FieldLabel}" />
<ComboBox
ItemsSource="{Binding Heights}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Height}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Weight" Style="{StaticResource FieldLabel}" />
<ComboBox
ItemsSource="{Binding Weights}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Weight}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="BMI" Style="{StaticResource FieldLabel}" />
<TextBox IsReadOnly="True" Text="{Binding BMI}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
C# code behind (I added no code at all here):
using System;
using System.Windows;
namespace TestApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
BMIViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TestApplication
{
public class BMIListItem
{
public BMIListItem(string name, float value)
{
Name = name;
Value = value;
}
public BMIListItem(float value)
{
Name = value.ToString();
Value = value;
}
public String Name { get; set; }
public float Value { get; set; }
}
public class BMIViewModel : INotifyPropertyChanged
{
public BMIViewModel()
{
// Of course you would write loops for these in real life.
// You should not need help with that.
Heights = new List<BMIListItem>
{
new BMIListItem("Bitte auswählen", float.NaN),
new BMIListItem("Dummy", 0),
new BMIListItem(150),
new BMIListItem(151),
new BMIListItem(152),
// etc.
};
Weights = new List<BMIListItem>
{
new BMIListItem("Bitte auswählen", float.NaN),
new BMIListItem("Dummy", 0),
new BMIListItem(40),
new BMIListItem(41),
new BMIListItem(42),
// etc.
};
}
public List<BMIListItem> Heights { get; private set; }
public List<BMIListItem> Weights { get; private set; }
#region BMI Property
private float _bmi = 0;
public float BMI
{
get { return _bmi; }
set
{
if (value != _bmi)
{
_bmi = value;
OnPropertyChanged("BMI");
}
}
}
#endregion BMI Property
#region Height Property
private float _height = float.NaN;
public float Height
{
get { return _height; }
set
{
if (value != _height)
{
_height = value;
UpdateBMI();
OnPropertyChanged("Height");
}
}
}
#endregion Height Property
#region Weight Property
private float _weight = float.NaN;
public float Weight
{
get { return _weight; }
set
{
if (value != _weight)
{
_weight = value;
UpdateBMI();
OnPropertyChanged("Weight");
}
}
}
#endregion Weight Property
private void UpdateBMI()
{
if (float.IsNaN(Weight) || float.IsNaN(Height) || Height == 0)
{
BMI = 0;
}
else
{
BMI = Weight / Height;
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propName)
{
var handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
#endregion INotifyPropertyChanged
}
}

Related

IValueConverter not "converting" upon item being added to list [duplicate]

I’m learning C# and building a UI that reads and writes integers to an XML config file. The UI uses a variety of custom user controls. I have a 3 radiobutton user control that binds to a single int variable (control returns 0,1,2). The control uses an event to trigger the update. It looks at the 3 isChecked properties to determine the new int value. But I don’t know how to update the original binding value from the code behind. It's once removed so to speak because there are two binds..one in the main window and one in the user control. As a beginner am lost at this point. BTW reading the int value into the 3 radiobuttons is working using a converter.
here is the user control xaml.cs...
namespace btsRV7config.controls
{
public partial class ui3X : UserControl
{
public ui3X()
{
InitializeComponent();
}
void _event(object sender, RoutedEventArgs e)
{
int newValue = 0;
if (rdbttn1.IsChecked == true) { newValue = 0; }
else if (rdbttn2.IsChecked == true) { newValue = 1; }
else if (rdbttn3.IsChecked == true) { newValue = 2; }
txtb.Text = newValue.ToString(); //tempRemove
// !!! assign newValue to Binding Source !!!
//---------------------------------------------
uiBinding1 = newValue;
BindingOperations.GetBindingExpression(rdbttn1, RadioButton.IsCheckedProperty).UpdateSource();
//---------------------------------------------
}
public static readonly DependencyProperty uiBinding1Property = DependencyProperty.Register("uiBinding1", typeof(int), typeof(ui3X));
public int uiBinding1
{
get { return (int)GetValue(uiBinding1Property); }
set { SetValue(uiBinding1Property, value); }
}
public static readonly DependencyProperty uiBinding2Property = DependencyProperty.Register("uiBinding2", typeof(int), typeof(ui3X));
public int uiBinding2
{
get { return (int)GetValue(uiBinding2Property); }
set { SetValue(uiBinding2Property, value); }
}
public static readonly DependencyProperty uiBinding3Property = DependencyProperty.Register("uiBinding3", typeof(int), typeof(ui3X));
public int uiBinding3
{
get { return (int)GetValue(uiBinding3Property); }
set { SetValue(uiBinding3Property, value); }
}
}
}
here is user control xaml
<UserControl x:Class="btsRV7config.controls.ui3X"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" Height="22">
<RadioButton Name="rdbttn1" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding1}"
Click="_event" />
<RadioButton Name="rdbttn2" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding2}"
Click="_event" />
<RadioButton Name="rdbttn3" VerticalAlignment="Center"
IsChecked="{Binding ElementName=root, Path=uiBinding3}"
Click="_event" />
<TextBox Name="txtb" Margin="5 0 0 0" Width="20" Height="17" /> <!-- tempRemove -->
</StackPanel>
</UserControl>
here is an example of the user control used in MainWindow.xaml
<Window x:Class="btsRV7config.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:btsRV7config.controls"
xmlns:converter="clr-namespace:btsRV7config.converters"
Title="Vans RV7 Configuration" Height="350" Width="525" >
<Window.Resources>
<converter:Int_Int_Bool_Converter x:Key="Int_Int_Bool" />
</Window.Resources>
<Grid>
<controls:ui3X uiName="Font Color" ui1="Black" ui2="Green" ui3="Cyan"
uiBinding1="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=0}"
uiBinding2="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=1}"
uiBinding3="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=2}" />
</Grid>
</Window>
here is MainWindow.xaml.cs
namespace btsRV7config
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
record data = new record();
DataContext = data;
}
}
public class record : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _RV7sld_DFfontColor = RV7sld_dict["DFfontColor"];
public int RV7sld_DFfontColor
{
get
{ return _RV7sld_DFfontColor; }
set
{
_RV7sld_DFfontColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("RV7sld_DFfontColor"));
}
}
}
}
}
Sorry for posting so much code - I think the important is the user controls xaml.cs at top.
here is a link to a picture of the UI.
I've simplified the code I've posted to fit.
http://www.baytower.ca/photo/uiSample.jpg
So - 'Font Color'(RV7sld_DFfontColor) can be black(0) green(1) cyan(2)
Danny
The BindingOperations class enables you to "force" the binding updates in either direction.
Let's say there is a string property X in the code behind and there is a TextBox in XAML, bound to that property:
// C#:
public string X { get; set; }
// XAML:
<TextBox Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=X}" />
To copy from textBox1.Text to X do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateSource();
To copy from X to textBox1.Text do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateTarget();

How to bind WPF ItemsControl source to list segment

I have an ItemsControl that is bound to a list of elements. How can I have it display only a segment of that list, say, based on a given Offset and Count ?
Obviously I could declare an IEnumerable property as MyList.Skip(Offset).Take(Count), use that in the binding and notify PropertyChanged each time Offset or Count are modified. That works fine, except that each time the ItemsControl would recreate the views for all of its items, even for those that aren't new.
I guess a more idiomatic way would be to have a CollectionView but it doesn't seem to support the concept of current segment. I tried to use an index-based filter but besides from feeling wrong, it suffers from the same performance problem as before.
So the only way I saw so far was to maintain an ObservableCollection with the correct list elements and use this in the binding, but shouldn't there be an easier way?
Here's my test program (I know the code for MyObservableCollection does not work in the general case)
MainWindow.xaml
<Window x:Class="ItemsControlSegmentView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ItemsControlSegmentView"
x:Name="This"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="ItemsControl">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" Background="{Binding DataContext.CurrentColor, ElementName=This, Mode=OneTime}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding MyEnumerable}"/>
<ItemsControl ItemsSource="{Binding MyCollectionView}"/>
<ItemsControl ItemsSource="{Binding MyObservableCollection}"/>
</StackPanel>
<Slider Maximum="{Binding MyList.Count}" Value="{Binding Offset}"/>
<Slider Maximum="{Binding MaxCount}" Value="{Binding Count}"/>
</StackPanel>
</Window>
MainWindowViewModel
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Data;
using JetBrains.Annotations;
namespace ItemsControlSegmentView
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private int offset;
private int count = 7;
private readonly Random random = new Random();
public string CurrentColor { get; private set; } = "White";
public int Offset
{
get { return offset; }
set
{
if (offset == value) return;
var atEnd = value > offset;
offset = value;
UpdateAllViews(atEnd);
OnPropertyChanged(nameof(MaxCount));
OnPropertyChanged();
}
}
public int Count
{
get { return count; }
set
{
if (count == value) return;
count = value;
UpdateAllViews(true);
OnPropertyChanged();
}
}
public int MaxCount => MyList.Count - Offset;
public List<string> MyList { get; } = new List<string>(Enum.GetNames(typeof(DayOfWeek)));
private void UpdateAllViews(bool atEnd)
{
CurrentColor = $"#{random.Next():X8}";
UpdateMyEnumerable();
UpdateMyCollectionView();
UpdateMyObservableCollection(atEnd);
}
public IEnumerable<string> MyEnumerable => MyList.Skip(Offset).Take(Count);
private void UpdateMyEnumerable() => OnPropertyChanged(nameof(MyEnumerable));
public ListCollectionView MyCollectionView { get; }
private void UpdateMyCollectionView() => MyCollectionView.Filter = entry =>
{
var indexOf = MyList.IndexOf((string)entry);
return indexOf >= Offset && indexOf < Offset + Count;
};
public ObservableCollection<string> MyObservableCollection { get; }
private void UpdateMyObservableCollection(bool atEnd)
{
foreach (var s in MyList.Except(MyEnumerable))
MyObservableCollection.Remove(s);
var i = 0;
foreach (var s in MyEnumerable.Except(MyObservableCollection))
if (atEnd) MyObservableCollection.Add(s);
else MyObservableCollection.Insert(i++, s);
}
public MainWindowViewModel()
{
MyCollectionView = new ListCollectionView(MyList);
MyObservableCollection = new ObservableCollection<string>(MyList);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is how the window looks like after having scrolled the offset slider to the right end and back, step by step. Only the third ItemsControl keeps its existing entries:

How to avoid recursive loops when using data binding in WPF?

As somewhat contrived example consider a simple FX calculator having amounts in two different currencies and a rate to covert between them. The rules are then when either amount is changed the rate is calculates and if the rate is changed then the second amount is calculated from the first amount and the exchange rate.
With the implementation below which has all the interaction logic in the view model, changing any amount in the GUI results in a mutually recursive loop.
One way to attempt to fix it would be be to add checks on setter for the model so that an event is not raised when setting a property to its existing value which is in any case good practice. However this is not a foolproof solution in itself as with floating point numbers there is always the possibility that there is a small rounding error which results in an event being raised.
In a world without data binding updates to the model and other text boxes could be done in the LostFocus event of the text box that changed which would not trigger any further event as we only responding to user events not changes in the data.
Another way I thought of would be to have flags to indicate a certain field is being updated programmatically and ignore changes to that field when the flag is set but that soon becomes messy when a lot of fields are involved.
Are there are any standard techniques or patterns which are used to address this issue in WPF apps?
The view model
namespace LoopingUpdates
{
public class FxModel : INotifyPropertyChanged
{
private double _amountCcy1;
private double _amountCcy2;
private double _rate;
public double AmountCcy1
{
get { return _amountCcy1; }
set
{
_amountCcy1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy1"));
}
}
public double AmountCcy2
{
get { return _amountCcy2; }
set
{
_amountCcy2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy2"));
}
}
public double Rate
{
get { return _rate; }
set
{
_rate = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Rate"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ViewModel
{
public FxModel FxModel { get; set; }
public ViewModel()
{
FxModel = new FxModel() { AmountCcy1 = 100, AmountCcy2 = 200, Rate = 2 };
FxModel.PropertyChanged += FxModel_PropertyChanged;
}
private void FxModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName) {
case "AmountCcy1":
Debug.WriteLine("Amount Ccy 1 changed");
FxModel.Rate = FxModel.AmountCcy2 / FxModel.AmountCcy1;
break;
case "AmountCcy2":
Debug.WriteLine("Amount Ccy 2 changed");
FxModel.Rate = FxModel.AmountCcy2 / FxModel.AmountCcy1;
break;
case "Rate":
Debug.WriteLine("Rate 1 changed");
FxModel.AmountCcy2 = FxModel.AmountCcy1 * FxModel.Rate;
break;
}
}
}
}
The window xaml
<Window x:Class="LoopingUpdates.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:LoopingUpdates"
mc:Ignorable="d"
Title="MainWindow" Height="148.7" Width="255.556" Loaded="Window_Loaded">
<Grid>
<Label x:Name="label" Content="Amount Ccy 1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="label1" Content="Amount Ccy 2" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/>
<Label x:Name="label2" Content="Rate" HorizontalAlignment="Left" Margin="10,72,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txtAmountCcy1" Text="{Binding FxModel.AmountCcy1}" HorizontalAlignment="Left" Height="26" Margin="99,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" />
<TextBox x:Name="txtAmountCcy2" Text="{Binding FxModel.AmountCcy2}" HorizontalAlignment="Left" Height="26" Margin="99,41,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" />
<TextBox x:Name="txtRate" Text="{Binding FxModel.Rate}" HorizontalAlignment="Left" Height="26" Margin="99,72,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="72" />
</Grid>
</Window>
The window code behind
namespace LoopingUpdates
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = new ViewModel();
}
}
}
Good question.
I see two ways to face that problem:
Create a Property IsUpdating and do not handle PropertyChanged if the IsUpdating is true. Then you can "deactivate" updating process...
Create a second property for each one (e.g. RateInternal, AmountCcy2Internal, ...) that doesn't call property changed.
These options are not ideal, but I don't know a better way.
I always avoid recursive loops checking if (value != _privateField) inside the setters of my ViewModel's properties.
If you think rounding may be a problem, I would just change the values of the fields and call PropertyChanged if the rounded values are different:
public double AmountCcy1
{
get { return _amountCcy1; }
set
{
if (Math.Round(value, 2) != Math.Round(_amountCcy1, 2))
{
_amountCcy1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy1"));
}
}
}
public double AmountCcy2
{
get { return _amountCcy2; }
set
{
if (Math.Round(value, 2) != Math.Round(_amountCcy2, 2))
{
_amountCcy2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AmountCcy2"));
}
}
}
There's nothing wrong with putting a check in your property setter such as
if (property == value)
return;
And therefore not setting the property or raising the property changed event. If rounding is what you're afraid of then I would take care of the rounding in the ViewModel also.

Aligning Text with Section of Text Box

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.

setting the DataContex correctly

im building a UserControl MyUserControl that has his own ViewModel MyUserControlViewModel. MyUserControl contains 6 VehicleSelectionBlock (V1, ... V6). VehicleSelectionBlock is a UserControl i've made. it has 3 RadioButton: car, train, bus; all are of enum type Vehicle and of the same GroupName VehicleGroup.
my goal is to represent each of MyUserControl's VehicleSelectionBlocks in MyUserControlViewModel.
to make my self clear: in MyUserControlViewModel i want to be able to know&change what RadioButton is checked in every one of the 6 VehicleSelectionBlock. i think my main problem is not the converter but rather the DataContex - i'm not sure how to set it correctly for each of the controllers.
iv'e tried Binding (which is the obvious solution). i tried reading here, here , and here. unfortunately neither one helped my acheive my goal.
my code is below - im kinda new to wpf and data binding in generally. i've read almost every chapter in this tutorial but still lost sometimes.
please help me get through this and understand better the DataContex concept.
ty
MyUserContlor.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for MyUserContlor.xaml
/// </summary>
public partial class MyUserContlor : UserControl
{
public MyUserContlorViewModel ViewModel { get; set; }
public MyUserContlor()
{
ViewModel = new MyUserContlorViewModel();
InitializeComponent();
this.DataContext = ViewModel;
}
private void BtnImReady_OnClick(object sender, RoutedEventArgs e)
{
//this code is irrelevant to the question
throw NotImplementedException();
}
}
}
MyUserContlor.xaml:
<UserControl x:Class="Project01.MyUserContlor"
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:loc="clr-namespace:Project01"
mc:Ignorable="d"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<StackPanel>
<loc:VehicleSelectionBlock Name="V1"/>
<loc:VehicleSelectionBlock Name="V2"/>
<loc:VehicleSelectionBlock Name="V3"/>
<loc:VehicleSelectionBlock Name="V4"/>
<loc:VehicleSelectionBlock Name="V5"/>
<loc:VehicleSelectionBlock Name="V6"/>
<Button x:Name="BtnImReady" Click="BtnImReady_OnClick">Im Ready!</Button>
</StackPanel>
</Viewbox>
</UserControl>
MyUserContlorViewModel.cs:
namespace Project01
{
public class MyUserContlorViewModel : INotifyPropertyChanged
{
public MyUserContlorViewModel()
{
VehicleArr = new MyViewModel_Vehicle[6];
PropertyChanged+=MyUserControlViewModel_PropertyChanged;
}
public MyViewModel_Vehicle[] VehicleArr;
public event PropertyChangedEventHandler PropertyChanged;
public PropertyChangedEventHandler GetPropertyChangedEventHandler() { return PropertyChanged; }
private void MyUserControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//might be useful
throw NotImplementedException();
}
}
//this class should represent a VehicleSelectionBlock
public class MyViewModel_Vehicle
{
public Vehicle VehicleSelected {get; set;}
MyViewModel_Vehicle(){}
MyViewModel_Vehicle(Vehicle v){ VehicleSelected = v;}
}
}
VehicleSelectionBlock.xaml:
<UserControl x:Class="Project01.VehicleSelectionBlock"
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:Project01"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Background="GhostWhite"
BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel >
<Label Content="{Binding Name}"
FontWeight="Bold" HorizontalContentAlignment="Center"></Label>
<RadioButton GroupName="VehicleGroup" >car</RadioButton>
<RadioButton GroupName="VehicleGroup">train</RadioButton>
<RadioButton GroupName="VehicleGroup" IsChecked="True">bus</RadioButton>
</StackPanel>
</Border>
</Grid>
</UserControl>
VehicleSelectionBlock.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for VehicleSelectionBlock.xaml
/// </summary>
public partial class VehicleSelectionBlock : UserControl
{
public VehicleSelectionBlock()
{
InitializeComponent();
}
public VehicleSelectionBlock(String name)
{
name = Name;
InitializeComponent();
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name", typeof (String), typeof (VehicleSelectionBlock), new PropertyMetadata(default(String)));
public String Name
{
get { return (String) GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
public enum Vehicle { Car, Train, Bus}
}
here is a quick solution. keep in mind that the code needs to change if you want to add more values to your Vehicle enum.
the MyUserControlViewModel.cs file
public class MyUserControlViewModel
{
public MyUserControlViewModel()
{
VehicleArr = new VehicleViewModel[6];
for (int i = 0; i < 6;i++ )
VehicleArr[i] = new VehicleViewModel();
}
public VehicleViewModel[] VehicleArr { get; set; }
}
this will expose your 6 items. They could be more. As a result they will be displayed in an ItemsControl, as you will see later.
public class VehicleViewModel:ViewModelBase
{
private bool isCar, isTrain, isBus;
public bool IsCar
{
get { return isCar; }
set
{
if (isCar == value) return;
isCar = value;
OnChanged("IsCar");
}
}
public bool IsTrain
{
get { return isTrain; }
set
{
if (isTrain == value) return;
isTrain = value;
OnChanged("IsTrain");
}
}
public bool IsBus
{
get { return isBus; }
set
{
if (isBus == value) return;
isBus = value;
OnChanged("IsBus");
}
}
}
instances of VehicleViewModel will contain your radio selection using 3 bool properties. this is the solution disadvantage. If you want more values you'll have to add more properties. you can see this inherits ViewModelBase. ViewModelBase just implements INPC so i'm not going to put it here. ViewModelBase also exposes the OnChange method that triggers the INPC event.
displaying the list can be done in your MyUserControl by using an ItemsControl like below.
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<loc:VehicleControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
each item is also a UserControl. The VehicleControl user control is just a StackPanel that displays the RadioButons. This can be seen below.
<StackPanel Orientation="Horizontal">
<RadioButton Content="Car" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsCar, Mode=TwoWay}"/>
<RadioButton Content="Train" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsTrain, Mode=TwoWay}"/>
<RadioButton Content="Bus" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsBus, Mode=TwoWay}"/>
</StackPanel>
please notice that each RadioButton is bound to one of the 3 properties in the VehicleViewModel instance.
Once you press your button you should have all the selections recorded. if you want you could have a function that returns an enum value by analysing the 3 bool properties if that is what you need.
the best solution will be to get rid of the radio buttons and replace them with combo boxes. in this way you can change the enum members and everything will continue to work without changing anything else. this might look as below.
public class VehicleViewModel:ViewModelBase
{
private Vehicle selOption;
private readonly Vehicle[] options;
public VehicleViewModel()
{
this.options = (Vehicle[])Enum.GetValues(typeof(Vehicle));
}
public Vehicle[] Options { get { return options; } }
public Vehicle SelectedOption
{
get { return selOption; }
set
{
if (selOption == value) return;
selOption = value;
OnChanged("SelectedOption");
}
}
}
and for the view:
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can do directly in the code-behind of your control (in the default constructor)
public VehicleSelectionBlock()
{
InitializeComponent();
this.DataContext = new MyUserContlorViewModel ();
}
You can also do that in XAML (http://msdn.microsoft.com/en-us/library/ms746695(v=vs.110).aspx) declaration, as you wish.

Categories

Resources