Coding multiple viewmodels in WPF XAML MVVM - wpf

for my project, i am trying to create a signal channel generator which connects to a toolset and pushes signals into it.
the issue i have is that i have been given the project in a form where the code for the textboxes are in the codebehind file, and i would like them to be in the xaml.
i have a variable which controls the number of channels (viewmodels) which can be changed. which is able to create multiple instances of the same viewmodel on the window. this allows the ability to select different targets inside the tool whcih it is communicating with and be able to pump signals to each target.
here is the code currently in the XAML:
<Window x:Class="SigGeneratorMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SigGeneratorMVVM"
Title="Signal Generator" Height="370" Width="734" >
<StackPanel Name="MyWindow">
<!--<TextBox Height="23" HorizontalAlignment="Left" Margin="91,20,0,0" Name="CurrentValDisplay" VerticalAlignment="Top" Width="120" />-->
</StackPanel>
</Window>
here is the code for the mainwindow.cs
public partial class MainWindow : Window
{
List<ViewModel> gViewModels;
int gNumChannels = 1;
private System.Threading.Timer mViewUpdateTimer;
private TimerCallback mViewTimerCallback;
private UtilityParticipant mParticipant;
public MainWindow()
{
InitializeComponent();
// Connect as UtilityParticipant
ConnectMesh();
gViewModels = new List<ViewModel>();
for (int i = 0; i < gNumChannels; i++)
{
gViewModels.Add(new ViewModel(mParticipant));
TextBlock CurrentValueText = new TextBlock();
CurrentValueText.Text = "Current Value:";
CurrentValueText.Margin = new Thickness(5);
TextBox CurrentValueBox = new TextBox();
CurrentValueBox.Width = 120;
CurrentValueBox.Name = "CurrentValDisplay" + i.ToString();
CurrentValueBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
CurrentValueBox.Margin = new Thickness(10);
CurrentValueBox.SetBinding(TextBox.TextProperty, "CurrentValue");
//CurrentValDisplay.Name = "CurrentValDisplay" + i.ToString();
//CurrentValDisplay.SetBinding(TextBox.TextProperty, "CurrentValue");
TextBlock CurrentFrequencyText = new TextBlock();
CurrentFrequencyText.Text = "Frequency:";
CurrentFrequencyText.Margin = new Thickness(5);
TextBox CurrentFrequencyBox = new TextBox();
CurrentFrequencyBox.Width = 120;
CurrentFrequencyBox.Name = "CurrentFrequencyDisplay" + i.ToString();
CurrentFrequencyBox.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
CurrentFrequencyBox.Margin = new Thickness(10);
CurrentFrequencyBox.SetBinding(TextBox.TextProperty, "Frequency");
Slider FrequencySlider = new Slider();
FrequencySlider.Width = 200;
FrequencySlider.Name = "FrequencySet" + i.ToString();
FrequencySlider.Value= 10;
FrequencySlider.Maximum = 10;
FrequencySlider.Minimum = 0.1;
FrequencySlider.SetBinding(Slider.ValueProperty, "Frequency");
//Create a new stackpanel
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Vertical;
//Set DataContext of the StackPanel
sp.DataContext = gViewModels[i];
//Add controls created above to the StackPanel
sp.Children.Add(CurrentValueText);
sp.Children.Add(CurrentValueBox);
sp.Children.Add(CurrentFrequencyText);
sp.Children.Add(CurrentFrequencyBox);
sp.Children.Add(FrequencySlider);
//Add the StackPanel to the window
MyWindow.Children.Add(sp);
}
mViewTimerCallback = this.UpdateView;
mViewUpdateTimer = new System.Threading.Timer(mViewTimerCallback, null, 100, 20);
}
Update: I already have a ViewModel which has get set methods for each property (CurrentValue and Frequency for now), would it be sufficient to bind the DataTemplate and ItemsControl to that instead of creating a new model class?
private SigGenChannel mSigGenChannel;
//Constructor
public ViewModel(UtilityParticipant aParticipant)
{
mSigGenChannel = new SigGenChannel(aParticipant);
}
public string CurrentValue
{
get
{
return mSigGenChannel.CurrentValue.ToString();
}
set
{
mSigGenChannel.CurrentValue = double.Parse(value);
RaisePropertyChanged("CurrentValue");
}
}
public double Frequency
{
get
{
return mSigGenChannel.Frequency;
}
set
{
mSigGenChannel.Frequency = value;
RaisePropertyChanged("Frequency");
}
}
public double Amplitude
{
get
{
return mSigGenChannel.Amplitude;
}
set
{
mSigGenChannel.Amplitude = value;
RaisePropertyChanged("Amplitude");
}
}
public void RefreshValue()
{
//A bit of a cheat, but we provide a means to poke the Viewmodel
//And raise a property change event
RaisePropertyChanged("CurrentValue");
}
also this is the SigChannel model:
class SigGenChannel
{
#region Private members
private UtilityParticipant mParticipant;
private double mCurrentValue;
private double mFrequency;
private double mAmplitude;
private double mTarget;
private double mOffset;
private double mCurrentStepTime;
private DateTime mStartTime;
private System.Threading.Timer mTimer;
private TimerCallback mTCallback;
private int mUpdateInterval = 10;
#endregion
#region Public members
public double CurrentValue
{
get
{
return mCurrentValue;
}
set
{
mCurrentValue = value;
}
}
public double Frequency
{
get
{
return mFrequency;
}
set
{
mFrequency = value;
}
}
public double Amplitude
{
get
{
return mAmplitude;
}
set
{
mAmplitude = value;
}
}
public double Target
{
get
{
return mTarget;
}
set
{
mTarget = value;
}
}
#endregion
//Constructor
public SigGenChannel(UtilityParticipant aParticipant)
{
mParticipant = aParticipant;
mCurrentValue = 10;
mFrequency = 200;
mAmplitude = 100;
mOffset = 0;
mCurrentStepTime = 0;
mStartTime = DateTime.Now;
mTCallback = this.Update;
mTimer = new System.Threading.Timer(mTCallback, null, 500, mUpdateInterval);
//Array enumData = Enum.GetNames;
//RefreshItems();
//Temp Code....!
Collection lCollection = mParticipant.GetCollection("DefaultNodeName.NodeStats");
lCollection.Publish();
}
private void Update(object StateInfo)
{
TimeSpan span = DateTime.Now - mStartTime;
mCurrentStepTime = span.TotalMilliseconds / (double)1000;
mCurrentValue = (Math.Sin(mCurrentStepTime * (mFrequency * 2 * Math.PI)) * mAmplitude / 2) + mOffset;
//Temp Code...!
Collection lCollection = mParticipant.GetCollection("DefaultNodeName.NodeStats");
Parameter lParameter = lCollection.GetParameter("CPUPercent");
lParameter.SetValue(mCurrentValue);
lCollection.Send();

The current code is written in a way that does not follow the WPF suggested practices, and swimming against the current makes things a lot harder than then should be.
What the code should be doing is:
Create a (view)model class for a channel
For example:
class ChannelModel
{
public int Value { get; set; }
public int Frequency { get; set; }
}
Use an ItemsControl instead of a StackPanel
The WPF way of doing things like this is to bind controls to collections, so replace the StackPanel with an ItemsControl.
Bind the ItemsControl to an ObservableCollection of the models
Your main viewmodel should expose an ObservableCollection<ChannelModel> property and the control should bind to that directly:
<ItemsControl ItemsSource="{Binding CollectionOfChannelModels}"/>
This ensures that the control is automatically updated with any changes made to your collection without your needing to do anything else.
Use a DataTemplate to specify how each model should render
So far we 've gotten the control to stay in sync with your channel collection, but we also need to tell it how each item (channel model) should be displayed. To do this, add a DataTemplate to the Resources collection of the ItemsControl:
<ItemsControl.Resources>
<DataTemplate DataType={x:Type local:ChannelModel}>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Value" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>

Generally the idea is to create a DataTemplate for a specific type, in your case it is for ViewModel.
Create a DataTemplate for your ViewModel, for instance:
<DataTemplate DataType={x:Type local:ViewModel}>
<TextBox Text="{Binding ViewModelTextProperty}" />
</DataTemplate>
And also in your XAML you must bind your list of ViewModels
<ItemsControl ItemsSource="{Binding myListOfViewModels}"/>

Related

Wpf Decimals Styling

I want to Style a TextBox with decimal places like this:
How can I do that ?
You can extend the TextBox like this.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
public class DecimalTextBox : TextBox
{
public static readonly DependencyProperty FloatColorProperty = DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
public Color FloatColor
{
get { return (Color)GetValue(FloatColorProperty); }
set { SetValue(FloatColorProperty, value); }
}
protected TextBlock _textBlock;
protected FrameworkElement _textBoxView;
public DecimalTextBox()
{
_textBlock = new TextBlock() { Margin = new Thickness(1, 0, 0, 0) };
Loaded += ExTextBox_Loaded;
}
private void ExTextBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExTextBox_Loaded;
// hide the original drawing visuals, by setting opacity on their parent
var visual = this.GetChildOfType<DrawingVisual>();
_textBoxView = (FrameworkElement)visual.Parent;
_textBoxView.Opacity = 0;
// add textblock to do the text drawing for us
var grid = this.GetChildOfType<Grid>();
if (grid.Children.Count >= 2)
grid.Children.Insert(1, _textBlock);
else
grid.Children.Add(_textBlock);
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
_textBoxView.Opacity = 0;
_textBlock.Visibility = Visibility.Visible;
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
_textBoxView.Opacity = 1;
_textBlock.Visibility = Visibility.Collapsed;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
// making sure text on TextBlock is updated as per TextBox
var dotPos = Text.IndexOf('.');
var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
var textPart2 = (dotPos == -1 || dotPos >= (Text.Length-1)) ? null : Text.Substring(dotPos + 1);
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run {
Text = textPart1,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground });
if (textPart2 != null)
_textBlock.Inlines.Add(new Run {
Text = textPart2,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5/6,
Foreground = new SolidColorBrush(FloatColor) });
}
}
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
XAML code usage
<local:DecimalTextBox FloatColor="Maroon" />
And your output should look like this:
Update 05/17
Explanation: As you can see from the image, the DecimalTextBox displays the text in formatted mode only when its not focused.
I had initially developed the control to support formatting during edit (which can still be done by commenting the methods OnLostKeyboardFocus, and OnGotKeyboardFocus) - but because of the font-size difference the cursor positioning was getting slightly skewed, which in turn would translate to bad user experience.
Therefore, implemented the swap logic during GotFocus and LostFocus to fix that.
You can't do that with a TextBox, because TextBox only accepts color changes to the entire text. You should try with RichTextBox, that allows loop throug TextRange's. Look at this sample of syntax highlighting with a RichTextBox.
I actually understood how it works and made it:
private void richTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
richTextBox.TextChanged -= this.richTextBox_TextChanged;
if (richTextBox.Document == null)
return;
TextRange documentRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
documentRange.ClearAllProperties();
int dotIndex = documentRange.Text.IndexOf(".");
if(dotIndex == -1)
{
richTextBox.TextChanged += this.richTextBox_TextChanged;
return;
}
TextPointer dotStart = GetPoint(richTextBox.Document.ContentStart, dotIndex);
TextPointer dotEnd = dotStart.GetPositionAtOffset(1, LogicalDirection.Forward);
TextRange initRange = new TextRange(richTextBox.Document.ContentStart, dotStart);
TextRange endRange = new TextRange(dotEnd, richTextBox.Document.ContentEnd);
endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
richTextBox.TextChanged += this.richTextBox_TextChanged;
}
Suscribe the textbox TextChanged event to this method. You can now set the styles you want to every part of the text like this:
To change the last part (after the dot char) endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
To change the first part (before the dot char) is the same but for the initRange variable. If you want to change the 'dot' style, then create a new TextRange with dotStart and dotEnd TextPointers and apply styles to it. You can do other things like change font style, size, etc.
This code result looks like this:
All this is just for style. For checking that is a number is up to you.
I would define a custom control with this main properties:
FloatNumber: the original number, that should be a DependencyProperty to be Bind from the control.
NumberOfDecimalDigits: a number to choose how many decimal digits to represent, that should be a DependencyProperty to be Bind from the control.
FirstPart: a string that will contain the first part of the decimal number
Decimals: a string that will contain the decimal digits of FloatNumber
Of course this is just a scratch, those properties could be implemented better to extract FloatNumber parts.
public partial class DecimalDisplayControl : UserControl, INotifyPropertyChanged
{
public DecimalDisplayControl()
{
InitializeComponent();
(Content as FrameworkElement).DataContext = this;
}
public static readonly DependencyProperty NumberOfDecimalDigitsProperty =
DependencyProperty.Register(
"NumberOfDecimalDigits", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
public static readonly DependencyProperty FloatNumberProperty =
DependencyProperty.Register(
"FloatNumber", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
private static void OnFloatNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as DecimalDisplayControl).OnFloatNumberChanged();
}
protected void OnFloatNumberChanged()
{
int numberOfDecimalDigits = Convert.ToInt32(NumberOfDecimalDigits);
float fullNumber = Convert.ToSingle(FloatNumber);
float firstPart = (float)Math.Truncate(fullNumber);
float fullDecimalPart = fullNumber - firstPart;
int desideredDecimalPart = (int)(fullDecimalPart * Math.Pow(10, numberOfDecimalDigits));
FirstPart = $"{firstPart}.";
Decimals = desideredDecimalPart.ToString();
}
public string FloatNumber
{
get => (string)GetValue(FloatNumberProperty);
set { SetValue(FloatNumberProperty, value); }
}
public string NumberOfDecimalDigits
{
get => (string)GetValue(NumberOfDecimalDigitsProperty);
set { SetValue(NumberOfDecimalDigitsProperty, value); }
}
private string _firstPart;
public string FirstPart
{
get => _firstPart;
set
{
if (_firstPart == value)
return;
_firstPart = value;
OnPropertyChanged();
}
}
private string _decimals;
public string Decimals
{
get => _decimals;
set
{
if (_decimals == value)
return;
_decimals = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Its XAML:
<UserControl
x:Class="WpfApp1.CustomControls.DecimalDisplayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
FontSize="20"
Foreground="Black"
Text="{Binding FirstPart}" />
<TextBlock
FontSize="10"
Foreground="Red"
Text="{Binding Decimals}"
TextDecorations="Underline" />
</StackPanel>
</UserControl>
Then you can use it in your page and bind a property to make it change dynamically:
<Grid>
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<customControls:DecimalDisplayControl
HorizontalAlignment="Center"
VerticalAlignment="Center"
NumberOfDecimalDigits="2"
FloatNumber="{Binding MyNumber}" />
<TextBox
Width="200"
VerticalAlignment="Center"
Text="{Binding MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
The final result:

WPF user control, access dependency properties of component elements

Problem
A user WPF control is made up of multiple standard controls.
How can multiple dependency properties of the component (base or standard) controls be accessed in XAML, when implementing the parent (user) control, without creating additional properties?
Details
What do I mean by "creating additional dependency properties"? Well, that is the only way I know of accessing properties of the component controls: by implementing attached properties, as described at MSDN here.
However, it presents the following problems:
Existing dependency properties must be copied as new properties, defeating the DRY principle.
If data binding is to occur, more work must be done to bind existing dependency properties to the new exposed dependency properties.
I'm wondering if there is a way to "walk" the base controls within the user control, to access their properties - from within XAML.
Example
For example, I make a user WPF control that inherits from UserControl. It is simple - it consists of a StackPanel containing a Label and a TextBlock:
<UserControl x:Class="MyApp.CustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Label Name="BaseLabel">Label Here</Label>
<TextBlock Name="BaseTextBlock">Some text here.</TextBlock>
</StackPanel>
</UserControl>
Now, when I use my UserControl elsewhere in XAML, I'm wishfully thinking something like this could be done to edit my Label's content... although I don't know of a way:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">
<StackPanel>
<!-- This won't work, don't try at home kids. -->
<local:CustomControl BaseLabel.Content="I did it!"></local:CustomControl>
</StackPanel>
</Window>
Much thanks.
How about the next solution:
1. Create the AttachedProperty (because you must an entry point) and bind this property to the collection of data.This collection of data will contain changes you want perform on sub-controls of a main user control used inside the window. This collection will be defined inside the main window view model.
2. In attached property changed callback get the binded collection, parse it data into sub-controls properties.
Here is the solution:
3. Xaml code:
<Window.DataContext>
<nirHelpingOvalButton:MainWindowViewModel />
</Window.DataContext>
<Grid>
<nirHelpingOvalButton:InnerControl x:Name="MyInnerControl"
nirHelpingOvalButton:Helper.InnerControlPropertiesAccessor="{Binding InnerData, Mode=Default, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
4. Attached property code (bindig support):
public static readonly DependencyProperty InnerControlPropertiesAccessorProperty = DependencyProperty.RegisterAttached(
"InnerControlPropertiesAccessor", typeof (ObservableCollection<TargetControlData>), typeof (Helper), new PropertyMetadata(default(ObservableCollection<TargetControlData>), InnerValueAccessProviderPropertyChangedCallback));
public static void SetInnerControlPropertiesAccessor(DependencyObject element, ObservableCollection<TargetControlData> value)
{
element.SetValue(InnerControlPropertiesAccessorProperty, value);
}
public static ObservableCollection<TargetControlData> GetInnerControlPropertiesAccessor(DependencyObject element)
{
return (ObservableCollection<TargetControlData>) element.GetValue(InnerControlPropertiesAccessorProperty);
}
private static void InnerValueAccessProviderPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as Control;
if (control == null) return;
var valuesMap = args.NewValue as ObservableCollection<TargetControlData>;
if (valuesMap == null)
return;
valuesMap.ToList().ForEach(data => TryToBind(control, data));
}
private static void TryToBind(Control control, TargetControlData data)
{
var innerControl = control.FindName(data.SubControlName) as DependencyObject;
if (innerControl == null) return;
var myBinding = new Binding
{
Source = data,
Path = new PropertyPath("Data"),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
var descriptors = TypeDescriptor.GetProperties(innerControl);
var propertyDescriptor = descriptors.Find(data.SubConrolProperty, true);
var descriptor = DependencyPropertyDescriptor.FromProperty(propertyDescriptor);
if (descriptor == null) return;
var dependencyProperty = descriptor.DependencyProperty;
BindingOperations.SetBinding(innerControl, dependencyProperty, myBinding);
}
5. Inner control xaml:
<UserControl x:Class="NirHelpingOvalButton.InnerControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UniformGrid>
<Button x:Name="InnerControlButton"></Button>
<TextBlock x:Name="InnerContentTextBlock"></TextBlock>
</UniformGrid>
6. ViewModel code:
public class MainWindowViewModel:BaseObservableObject
{
private static int _staticCount = 0;
private List<Brush> _list = new List<Brush> {Brushes.Green, Brushes.Red, Brushes.Blue};
public MainWindowViewModel()
{
InnerData = new ObservableCollection<TargetControlData>
{
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Content",
Data = "Click Me",
},
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Command",
Data = new RelayCommand(CommandMethod),
},
new TargetControlData
{
SubConrolProperty = "Text",
SubControlName = "InnerContentTextBlock",
Data = "Hello"
},
new TargetControlData
{
SubConrolProperty = "Background",
SubControlName = "InnerContentTextBlock",
Data = Brushes.Green
},
new TargetControlData
{
SubConrolProperty = "Foreground",
SubControlName = "InnerContentTextBlock",
Data = Brushes.White
},
};
}
private void CommandMethod()
{
_staticCount ++;
var backgroundData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Background");
var textData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Text");
if (backgroundData == null || textData == null) return;
var index = _staticCount%_list.Count;
backgroundData.Data = _list[index];
textData.Data = string.Format("{0} {1}", "Hello", backgroundData.Data);
}
public ObservableCollection<TargetControlData> InnerData { get; set; }}
7. TargetControlData code:
public class TargetControlData:BaseObservableObject
{
private string _subControlName;
private string _subConrolProperty;
private object _data;
public string SubControlName
{
get { return _subControlName; }
set
{
_subControlName = value;
OnPropertyChanged();
}
}
public string SubConrolProperty
{
get { return _subConrolProperty; }
set
{
_subConrolProperty = value;
OnPropertyChanged();
}
}
public object Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
}
Summary - you can pull control properties data from configuration file, or collect them by reflection.
regards,
The way you suggested - I don't think this would be possible.
But it can be done with normal properties, instead of dependency properties, something like:
UserControl xaml:
<StackPanel>
<TextBlock x:Name="tbOne"></TextBlock>
<TextBlock x:Name="tbTwo" Foreground="Red"></TextBlock>
</StackPanel>
UserControl code behind:
public string One
{
get
{
return this.tbOne.Text;
}
set
{
this.tbOne.Text = value;
}
}
public string Two
{
get
{
return this.tbTwo.Text;
}
set
{
this.tbTwo.Text = value;
}
}
and the usage of user control:
<local:UserControl1 One="test1" Two="test2"></local:UserControl1>

Unable binding from class on C#

Here is code of class:
public class listboxitem
{
public string textmenu { get; set; }
public string logomenu { get; set; }
}
But when im binding it on textbox that's not show...
I have these array:
private string[] Logo_menu_array = { "/Assets/star-6-48.ico", "/Assets/note-48.ico", "/Assets/note-48.ico", "medal-48.ico", "joystick-48.ico" };
private string[] Text_menu_array={"Phổ biến trên YouTuBe","Âm nhạc","Thể thao","Trò chơi"};
//load menu
public void Load_Menu()
{
List<listboxitem> text = new List<listboxitem>();
listboxitem items=new listboxitem();
for(int i=0;i<Text_menu_array.Length&& i<Logo_menu_array.Length;i++)
{
items.textmenu=i.ToString();
}
for(int j=0;j<Logo_menu_array.Length;j++)
{
items.logomenu = j.ToString();
}
text.Add(items);
}
This website didn't agreed show more code. Ready hard to ask these my question.
I added code:
<ListBox Name="lst_menu" Foreground="Red">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding logomenu}"></Image>
<TextBlock Text="{Binding textmenu}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And here to loaded:
public MainPage()
{
this.InitializeComponent();
//get menu
List<listboxitem> menu_list = new List<listboxitem>();
Load_Menu();
lst_menu.ItemsSource = menu_list;
}
A few things here...
Firstly, we need to see the XAML to see how you are binding this in your UI. I'm going to assume you have something like:
<ListBox ItemSource="{Binding Items}"/>
Then a template that presents these.
In your code you have a List object called text which exists only in your method. You need to assign this value to a property in your view model onto which you can bind - based on the above:
public List<ListItem> Items {get;set;}
This property should trigger PropertyChanged as defined in INotifyPropertyChanged so implement that on your class. Implementing INotifyPropertyChanged
That gets you the basics. If you want to dynamically control this collection - i.e. change items at runtime - you should investigate ObservableCollection.
EDIT:
Based upon your full code listing, using code behind. You are setting your lst_menu.ItemsSource to an empty List. Your Load_Menu() builds a collection but does not return it.
public List<listboxitem> Load_Menu()
{
List<listboxitem> text = new List<listboxitem>();
listboxitem items=new listboxitem();
for(int i=0;i<Text_menu_array.Length&& i<Logo_menu_array.Length;i++)
{
items.textmenu=i.ToString();
}
for(int j=0;j<Logo_menu_array.Length;j++)
{
items.logomenu = j.ToString();
}
text.Add(items);
return text;
// Note you will only ever return one item here - check the logic
}
Then in your constructor:
List<listboxitem> menu_list = Load_Menu();
lst_menu.ItemsSource = menu_list;
Assuming you want the arrays to be used to build your collection, try using the following for build menu:
List<listboxitem> text = new List<listboxitem>();
for(int i =0; i< Math.Min(Logo_menu_array.Length, Text_menu_array.Length, i++)
{
var l = new listboxitem();
l.logomenu = Logo_menu_array[i];
l.textmenu = Logo_menu_array[i];
}
return text;
I hope this helps.

How to bind ObservableCollection<T> on Datagrid?

I'm David. I don`t know to bind collection to datagrid in XAML for a WPF application.
below are classes.
class TestSetting(): INotifyChanged
{
private a
public double A
{
get a;
set a = value;
Notify("A");
}
private b
public double B
{
get b;
set b = value;
Notify("B");
}
private c
public double C
{
get c;
set c = value;
Notify("C");
}
}
class TestCollect():ObservableCollection<T> ,INotifyListener
{
}
above code is Psedo code.
DataContext has 7 items. So the grid will have 7 columns. Could someone please help me with an example or a code snippet.
If the datacontext contains a TestCollection, all that is needed is Setting the ItemsSource to {Binding}
I think that what you need is something like that :
Your viemodel :
public class ViewModel
{
public ViewModel()
{
SourceList = new ObservableCollection<BusinessAdapter>();
for (int i = 0; i < 50; i++)
{
SourceList.Add(new BusinessAdapter { BusinessProperty = "blabla_" + i });
}
}
public ObservableCollection<BusinessAdapter> SourceList { get; private set; }
}
You're view code behind
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
And then in your view. The important stuff here is ' ItemsSource="{Binding SourceList}" ' which basically means "the source collection of my listbox is the the collection of my datacontext (which is a Viewmodel object) named SourceList"
<ListView x:Name="listOne"
Grid.Column="0"
Width="50"
Height="200"
ItemsSource="{Binding SourceList}" />
I am a newbie, but I will venture my answer:
ObservableCollection<YourModel> yourdata = new ObservableCollection<YourModel>();
dataGrid.ItemsSource = yourdata;
The 2nd. statement performs the binding.

WPF: ComboBox SelectedIndex = -1?

I am using a MVVM Wizard with several pages. When I set a value in the combobox and go to the next page and switch back I want to reset the value I set before.
But all whats happening is that the combobox is empty at top and the index is -1 ?
What do I wrong?
<ComboBox ItemsSource="{Binding Path=LessonNumbers}" SelectedIndex="{Binding SelectedLessonNumber}" />
private ReadOnlyCollection<int> _lessonNumbers;
public ReadOnlyCollection<int> LessonNumbers
{
get
{
if (_lessonNumbers == null)
this.CreateLessonNumbers();
return _lessonNumbers;
}
}
private void CreateLessonNumbers()
{
var list = new List<int>();
for (int i = 1; i < 24; i++)
{
list.Add(i);
}
_lessonNumbers = new ReadOnlyCollection<int>(list);
}
private int _selectedLessonNumber;
public int SelectedLessonNumber
{
get { return _selectedLessonNumber; }
set
{
if (_selectedLessonNumber == value)
return;
_selectedLessonNumber = value;
this.OnPropertyChanged("SelectedLessonNumber");
}
}
UPDATE:
<ComboBox
SelectedIndex="0"
SelectedItem="{Binding SelectedWeeklyRotationNumber}"
ItemsSource="{Binding Path=WeeklyRotationNumbers}"
Height="23"
HorizontalAlignment="Left"
Margin="336,212,0,0"
VerticalAlignment="Top"
Width="121"
MaxDropDownHeight="100"
IsReadOnly="True"
IsTextSearchEnabled="False"
/>
private ReadOnlyCollection _weeklyRotationNumbers;
public ReadOnlyCollection WeeklyRotationNumbers
{
get
{
if (_weeklyRotationNumbers == null)
this.CreateWeeklyRotationNumbers();
return _weeklyRotationNumbers;
}
}
private void CreateWeeklyRotationNumbers()
{
var list = new List<string>();
list.Add("No rotation");
for (int i = 1; i < 16; i++)
list.Add(i.ToString());
_weeklyRotationNumbers = new ReadOnlyCollection<string>(list);
}
private string _selectedWeeklyRotationNumber;
public string SelectedWeeklyRotationNumber
{
get { return _selectedWeeklyRotationNumber; }
set
{
if (_selectedWeeklyRotationNumber == value)
return;
_selectedWeeklyRotationNumber = value;
this.RaisePropertyChanged("SelectedWeeklyRotationNumber");
Messenger.Default.Send<string>(value);
}
}
Again, what do I wrong or what is wrong with the string property?
Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=LessonNumbers}"
SelectedItem="{Binding SelectedLessonNumber}" />
UPDATE:
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
In my case I typically do that in the constructor of my view.
// this my class containing WeeklyRotationNumbers
private MainViewModel _mvm;
public MainView()
{
InitializeComponent();
_mvm = new MainViewModel();
DataContext = _mvm;
}
I added string to the read only collections:
private ReadOnlyCollection<string> _weeklyRotationNumbers;
public ReadOnlyCollection<string> WeeklyRotationNumbers
I also implemented the interface INotifyPropertyChanged which I think you did, but you are likely using
a different base class to handle the PropertyChanged event.
Everthing else I cut and paste from your code.

Resources