I have two textboxes inside an expander. I am validating the textbox for text value when a button is clicked. If the textbox is empty the textbox is given foucs.
What I want to achieve is for the expander to expand automatically when one of these texboxes gets focus.
I could not find any ways to do that on the internet. Is it possible to do so?
xaml :
<Grid>
<Expander Header="Textbox Expander">
<StackPanel Orientation="Vertical">
<TextBox Name="txtName" Height="30" Width="100" Background="WhiteSmoke" />
<TextBox Name="txtAge" Height="30" Width="100" Background="WhiteSmoke" />
</StackPanel>
</Expander>
<Button Name="btnDone" Content="Done" Width="50" Height="50" Click="btnDone_Click"/>
</Grid>
c# :
using System.Windows;
using System.Windows.Controls;
namespace TestExpanderFocus
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
Validation validation = new Validation();
if(validation.validate(ref txtName) && validation.validate(ref txtAge))
{
//Do Something.
}
}
}
}
EDIT : Since this validation class is in another application I cannot edit this.
//Seperate class in another application which cannot be edited
public class Validation
{
public bool validate(ref TextBox txtobj)
{
if (string.IsNullOrWhiteSpace(txtobj.Text))
{
MessageBox.Show("Please Enter Data..!");
txtobj.Focus();
return false;
}
return true;
}
}
What you wanted to achieve is actually pretty simple.
First give the Expander a name
<Expander x:Name="MyExpander" ... />
Second, in your validation, before you focus on the textbox, simply expand the Expander
MyExpander.IsExpanded = true;
...
-- EDIT to satisy the new requirement --
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnDone_Click(object sender, RoutedEventArgs e)
{
Validation validation = new Validation();
// since you know that the text will be focused when the validation fails
var result1 = validation.validate(ref txtName);
var result2 = validation.validate(ref txtAge);
MyExpander.IsExpanded = !result1 || !result2;
if(result1 && result2)
{
//Do Something.
}
}
}
But I must admit, this is not the nicest solution. There should be an easier way to just add Trigger to the Expander Style directly. (I will leave it to other people, since I don't have more time)
There is a textbox in my mainwindow.xaml, when I enter the textbox, I expect the label in my usercontrol, known as View1.xaml will be update accordingly. However I realise the event is not raise at all in the user control when I type the textbox, can you tell me which part is wrong?
The event is able to raise in TextBox_TextChanged_1
my MainWindow.XAML
<Window xmlns:my="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:testapplication" x:Class="testapplication.MainWindow"
Title="MainWindow" Height="964" Width="790">
<Grid >
<Button x:Name="OpenView1" Content="Open Window 1" HorizontalAlignment="Left" Margin="33,70,0,0" VerticalAlignment="Top" Width="111" RenderTransformOrigin="0.279,1.409" Click="OpenView1_Click"/>
<Button x:Name="OpenView2" Content="Open Window 2" HorizontalAlignment="Left" Margin="33,169,0,0" VerticalAlignment="Top" Width="111" Click="OpenView2_Click"/>
<Button x:Name="OpenView3" Content="Open Window 3" HorizontalAlignment="Left" Margin="33,259,0,0" VerticalAlignment="Top" Width="111" Click="OpenView3_Click"/>
<local:View1 x:Name="ViewOne" HorizontalAlignment="Left" Margin="33,332,0,0" VerticalAlignment="Top" Height="226" Width="204" Visibility="Hidden"/>
<local:View2 x:Name="ViewTwo" HorizontalAlignment="Left" Margin="284,332,0,0" VerticalAlignment="Top" Height="226" Width="208" Visibility="Hidden"/>
<local:View3 x:Name="ViewThree" HorizontalAlignment="Left" Margin="534,332,0,0" VerticalAlignment="Top" Height="226" Width="196" Visibility="Hidden"/>
<TextBox HorizontalAlignment="Left" Height="42" Margin="326,70,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="182" FontSize="22" TextChanged="TextBox_TextChanged_1"/>
</Grid>
</Window>
my MainWindow.cs
namespace testapplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
//InitializeComponent();
}
//event handler
public event EventHandler<EventArgs> changedText;
private void OpenView1_Click(object sender, RoutedEventArgs e)
{
ViewOne.Visibility = Visibility.Visible;
}
private void OpenView2_Click(object sender, RoutedEventArgs e)
{
ViewTwo.Visibility = Visibility.Visible;
}
private void OpenView3_Click(object sender, RoutedEventArgs e)
{
ViewThree.Visibility = Visibility.Visible;
}
private void TextBox_TextChanged_1(object sender, TextChangedEventArgs e)
{
if (changedText != null)
{
changedText(this, e);
}
}
}
}
This is my UserControl, known as View1.xaml, it is included in my MainWindow.Xaml
namespace testapplication
{
/// <summary>
/// Interaction logic for View1.xaml
/// </summary>
public partial class View1 : UserControl
{
private MainWindow newWindow = new MainWindow();
public View1()
{
InitializeComponent();
newWindow.changedText += newWindow_ChangeText;
}
void newWindow_ChangeText(object sender, EventArgs e)
{
ViewOnelabel.Content = "Happy";
}
}
}
The problem is my ViewOnelabel.Content = "Happy" did not execute at all, it remain unchanged
There are a few things I would like to point out.
The equivalent of a winforms label in wpf is a TextBlock. A wpf label is actually a type of contentcontrol. Hence the content property.
In wpf there are routed events. These "bubble" up ( and tunnel down ) the visual tree. That means you can handle an event in the window from a control in a usercontrol inside it.
But mainly.
I encourage you to look into the MVVM pattern.
I've put together some code which illustrates these points.
I'd recommend just using binding and mvvm though.
My MainWindow markup:
Title="MainWindow" Height="350" Width="525"
TextBoxBase.TextChanged="Window_TextChanged"
>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<Label Name="OutputLabel"/>
<TextBlock Text="{Binding OutputString}"/>
<local:UserControl1/>
</StackPanel>
</Grid>
Notice that it handles a textchanged event and because that's routing it will get the event from UserControl1 inside it.
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_TextChanged(object sender, TextChangedEventArgs e)
{
OutputLabel.Content = $"Happy {((TextBox)e.OriginalSource).Text}";
}
}
You don't do anything with the text from your textbox in your handler but I have some code there proves you could get at that from mainwindow if you wanted.
My viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private string inputString;
public string InputString
{
get { return inputString; }
set
{
inputString = value;
OutputString = $"{inputString.Length} characters entered";
RaisePropertyChanged();
}
}
private string outputString;
public string OutputString
{
get { return outputString; }
set
{
outputString = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Usercontrol1 just has a textbox:
<Grid>
<TextBox Text="{Binding InputString, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
As you type in that textbox, the text is transferred to the bound property in my viewmodel. That hits the code in my setter. This in turn sets OutputString which is bound to my textblock.
Text changes in both my label and textblock as I type.
Here's a link to my sample on onedrive
https://1drv.ms/u/s!AmPvL3r385QhgpgOPNKPs-veFJ2O3g
The main problem here is that your View1 class is subscribing to an event on a new MainWindow instance, not the MainWindow instance created by your application on start.
Since your MainWindow class has a reference to your View1 class (a named member "ViewOne") you should just change it from the MainWindow class.
private void TextBox_TextChanged_1(object sender, TextChangedEventArgs e)
{
ViewOne.ViewOneLabel.Content = "Happy";
}
Get rid of the chenagedText event handler and all the code in the View1.xaml.cs... you don't need it.
Note: I am hoping that you are just playing around and learning here... there is no way I would condone building a WPF application in this way.
You could only use the event of the MainPage. I recomment you to add a Property to the UserControl. In my case I call it Text.
public string Text
{
set { ViewOneLabel.Content = value; }
}
In the MainWindow use the Property within the TextChanged Event.
private void TextBox_TextChanged_1(object sender, TextChangedEventArgs e)
{
OpenView1.Text = TextBox.Text;
}
You are creating a new instance of MainWindow in your UserControl. What you want to do is to hook up an event handler to the instance that you actually see on the screen. You can get a reference to this one using the Window.GetWindow method:
public partial class View1 : UserControl
{
public View1()
{
InitializeComponent();
Loaded += (s, e) =>
{
Window mainWindow = Window.GetWindow(this) as MainWindow;
if(mainWindow != null)
mainWindow.changedText += newWindow_ChangeText;
};
}
void newWindow_ChangeText(object sender, EventArgs e)
{
ViewOnelabel.Content = "Happy";
}
}
I am working on an app that has a lot of buttons on the main window.
The buttons have been programmed individually to change color when pressed, and save that those colors using the user settings from Visual Studio.
More exactly, when the user presses a button once, its background changes to red, and when he presses it again the background changes to green.
Edited for mm8:
Here is the xaml (sample):
<Window x:Class="test2.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:test2"
xmlns:properties="clr-namespace:test2.Properties"
mc:Ignorable="d"
Title="MainWindow" WindowStartupLocation="CenterScreen" Height="850" Width="925">
<Grid x:Name="theGrid">
<Button x:Name="Button0" HorizontalAlignment="Left" Margin="197,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color0, Mode=TwoWay}" Click="Button0_Click"/>
<Button x:Name="Button1" HorizontalAlignment="Left" Margin="131,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color1, Mode=TwoWay}" Click="Button1_Click"/>
<Button x:Name="Button2" HorizontalAlignment="Left" Margin="263,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color2, Mode=TwoWay}" Click="Button2_Click"/>
<Button x:Name="Reset" Content="Reset" HorizontalAlignment="Left" Margin="832,788,0,0" VerticalAlignment="Top" Width="75" Click="Reset_Click" />
</Grid>
</Window>
And this is the code I implemented into each button's click event:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
namespace test2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button0_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color0 == "Green")
{
Properties.Settings.Default.Color0 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color0 = "Green";
Properties.Settings.Default.Save();
}
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color1 == "Green")
{
Properties.Settings.Default.Color1 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color1 = "Green";
Properties.Settings.Default.Save();
}
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color2 == "Green")
{
Properties.Settings.Default.Color2 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color2 = "Green";
Properties.Settings.Default.Save();
}
}
private void Reset_Click(object sender, RoutedEventArgs e)
{
foreach (Button button in theGrid.Children.OfType<Button>())
}
}
}
Now, I want to some sort of a Reset button, which when pressed changes the background of all the buttons to the default (not red, nor green).
What I tried to do was to use ideas from this thread and use them as a click event on the reset button, but whenever I do
foreach (Control x in Control.Controls)
or any other method using the "Controls" (this.Controls, etc) I get it underlined with red, saying that the Control class does not have the definition.
Am I doing something wrong? Do you guys have any suggestions as to how I can program that button to change all buttons' background to default?
The short version: you're doing it wrong. I mean, I suspect you already knew that to some extent, because the code didn't work. But looking at your comment that says you'll have 240 buttons, you are really going about this the wrong way.
This answer is meant to walk you through three different options, each moving you closer to what is the best approach for dealing with this scenario.
Starting with your original effort, we can get the code you posted to work mostly as-is. Your main problem is that, having successfully obtained each Button child of your Grid, you cannot just set the Button.Background property. If you do, you will erase the binding that was set up in the XAML.
Instead, you need to reset the values in your source data, and then force the binding target to be updated (because the Settings object does not provide a WPF-compatible property-changed notification mechanism). You can accomplish this by changing your Reset_Click() method to look like this:
private void Reset_Click(object sender, RoutedEventArgs e)
{
Settings.Default.Color0 = Settings.Default.Color1 = Settings.Default.Color2 = "";
Settings.Default.Save();
foreach (Button button in theGrid.Children.OfType<Button>())
{
BindingOperations.GetBindingExpression(button, Button.BackgroundProperty)?.UpdateTarget();
}
}
This is not ideal. It would be much better to not have to access the binding state directly, and instead let WPF deal with updates. In addition, if you look at the debug output, for every time a button is set to the "default" state, a exception is being thrown. That's also not a very good situation.
These issues can be addressed. The first, by moving to an MVVM-style implementation, in which the state of the program is stored independently of the visual part of the program, with the visual part responding to changes in that state. The second, by adding some logic to coerce the invalid string value into something that WPF is happy with.
To accomplish this, it's helpful to have a couple of pre-made helper classes made, one for supporting the view model classes themselves directly, and one for representing a command (which is a better way to deal with user input than handling Click events directly). Those look like this:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null) { }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke() ?? true;
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
These are just examples. The NotifyPropertyChangedBase class is mostly identical to what I use on a day-to-day basis. The DelegateCommand class is a stripped-down version of a more fully-featured implementation I use (mainly, it's missing support for command parameters, since they aren't needed in this particular scenario). There are lots of similar examples on Stack Overflow and the Internet, often built into a library designed to help with WPF development.
With those, we can define some "view model" classes that will represent the state of the program. Note that these classes have practically nothing in them that involves the view per se. The one exception being the use of DependencyProperty.UnsetValue, as a concession to simplicity. It is possible to get rid of even that, along with the "coerce" methods that support that design, as you'll see in the third example, after this one.
First, a view model to represent each individual button's state:
class ButtonViewModel : NotifyPropertyChangedBase
{
private object _color = DependencyProperty.UnsetValue;
public object Color
{
get { return _color; }
set { _UpdateField(ref _color, value); }
}
public ICommand ToggleCommand { get; }
public ButtonViewModel()
{
ToggleCommand = new DelegateCommand(_Toggle);
}
private void _Toggle()
{
Color = object.Equals(Color, "Green") ? "Red" : "Green";
}
public void Reset()
{
Color = DependencyProperty.UnsetValue;
}
}
Then a view model that holds the overall state of the program:
class MainViewModel : NotifyPropertyChangedBase
{
private ButtonViewModel _button0 = new ButtonViewModel();
public ButtonViewModel Button0
{
get { return _button0; }
set { _UpdateField(ref _button0, value); }
}
private ButtonViewModel _button1 = new ButtonViewModel();
public ButtonViewModel Button1
{
get { return _button1; }
set { _UpdateField(ref _button1, value); }
}
private ButtonViewModel _button2 = new ButtonViewModel();
public ButtonViewModel Button2
{
get { return _button2; }
set { _UpdateField(ref _button2, value); }
}
public ICommand ResetCommand { get; }
public MainViewModel()
{
ResetCommand = new DelegateCommand(_Reset);
Button0.Color = _CoerceColorString(Settings.Default.Color0);
Button1.Color = _CoerceColorString(Settings.Default.Color1);
Button2.Color = _CoerceColorString(Settings.Default.Color2);
Button0.PropertyChanged += (s, e) =>
{
Settings.Default.Color0 = _CoercePropertyValue(Button0.Color);
Settings.Default.Save();
};
Button1.PropertyChanged += (s, e) =>
{
Settings.Default.Color1 = _CoercePropertyValue(Button1.Color);
Settings.Default.Save();
};
Button2.PropertyChanged += (s, e) =>
{
Settings.Default.Color2 = _CoercePropertyValue(Button2.Color);
Settings.Default.Save();
};
}
private object _CoerceColorString(string color)
{
return !string.IsNullOrWhiteSpace(color) ? color : DependencyProperty.UnsetValue;
}
private string _CoercePropertyValue(object color)
{
string value = color as string;
return value ?? "";
}
private void _Reset()
{
Button0.Reset();
Button1.Reset();
Button2.Reset();
}
}
The important thing to note is that nowhere in the above does anything try to manipulate the UI objects directly, and yet you have everything there that you'd need to maintain the state of the program as controlled by the user.
With the view models in hand, all that's left is to define the UI:
<Window x:Class="WpfApp1.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:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Width="66" Height="26" Background="{Binding Button0.Color}" Command="{Binding Button0.ToggleCommand}"/>
<Button Width="66" Height="26" Background="{Binding Button1.Color}" Command="{Binding Button1.ToggleCommand}"/>
<Button Width="66" Height="26" Background="{Binding Button2.Color}" Command="{Binding Button2.ToggleCommand}"/>
</StackPanel>
<Button Content="Reset" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Command="{Binding ResetCommand}"/>
</Grid>
</Window>
Some things to note here:
There is no code at all in the MainWindow.xaml.cs file. It's completely unchanged from the default template, with just the parameterless constructor and the call to InitializeComponent(). By moving to an MVVM-style implementation, a lot of the internal plumbing required otherwise just goes away completely.
This code does not hard-code any UI element locations (e.g. by setting Margin values). Instead, it takes advantage of WPF's layout features to place the color buttons in a row in the middle, and to place the reset button in the lower right of the window (that way it's visible no matter what size the window is).
The MainViewModel object is set as the Window.DataContext value. This data context is inherited by any elements within the window, unless overridden by setting it explicitly, or (as you'll see in the third example) because the element is automatically generated in a different context. Binding paths are all relative to this object, of course.
Now, this would probably an okay way to go if you really did only have three buttons. But with 240, you're in for a lot of copy/paste headaches. There are a lot of reasons to follow the DRY ("don't repeat yourself") principle, including convenience and code reliability and maintainability. That all would definitely apply here.
To improve on the MVVM example above, we can do some things:
Save the settings in a collection instead of having an individual setting property for each button.
Maintain a collection of the ButtonViewModel objects instead of having an explicit property for each button.
Use an ItemsControl to present the collection of ButtonViewModel objects instead of declaring a separate Button element for every button.
To accomplish this, the view models will have to change a bit. The MainViewModel replaces the individual properties with a single Buttons property to hold all the button view model objects:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<ButtonViewModel> Buttons { get; } = new ObservableCollection<ButtonViewModel>();
public ICommand ResetCommand { get; }
public MainViewModel()
{
ResetCommand = new DelegateCommand(_Reset);
for (int i = 0; i < Settings.Default.Colors.Count; i++)
{
ButtonViewModel buttonModel = new ButtonViewModel(i) { Color = Settings.Default.Colors[i] };
Buttons.Add(buttonModel);
buttonModel.PropertyChanged += (s, e) =>
{
ButtonViewModel model = (ButtonViewModel)s;
Settings.Default.Colors[model.ButtonIndex] = model.Color;
Settings.Default.Save();
};
}
}
private void _Reset()
{
foreach (ButtonViewModel model in Buttons)
{
model.Reset();
}
}
}
You'll notice the handling of the Color property is a little different too. That's because in this example, the Color property is an actual string type instead of object, and I'm using an IValueConverter implementation to handle mapping the string value to what's needed by the XAML elements (more on that in a bit).
The new ButtonViewModel is a little different too. It has a new property, to indicate which button it is (this allows the main view model to know which element of the settings collection the button view model goes with), and the Color property handling is a little simpler, because now we're dealing only with string values, instead of the DependencyProperty.UnsetValue value as well:
class ButtonViewModel : NotifyPropertyChangedBase
{
public int ButtonIndex { get; }
private string _color;
public string Color
{
get { return _color; }
set { _UpdateField(ref _color, value); }
}
public ICommand ToggleCommand { get; }
public ButtonViewModel(int buttonIndex)
{
ButtonIndex = buttonIndex;
ToggleCommand = new DelegateCommand(_Toggle);
}
private void _Toggle()
{
Color = Color == "Green" ? "Red" : "Green";
}
public void Reset()
{
Color = null;
}
}
With our new view models, they can now be hooked up in the XAML:
<Window x:Class="WpfApp2.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:l="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Buttons}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<l:ColorStringConverter x:Key="colorStringConverter1"/>
<DataTemplate DataType="{x:Type l:ButtonViewModel}">
<Button Width="66" Height="26" Command="{Binding ToggleCommand}"
Background="{Binding Color, Converter={StaticResource colorStringConverter1}, Mode=OneWay}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<Button Content="Reset" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Command="{Binding ResetCommand}"/>
</Grid>
</Window>
As before, the main view model is declared as the Window.DataContext value. But, instead of explicitly declaring each button element explicitly, I'm using an ItemsControl element to present the buttons. It has these crucial aspects:
The ItemsSource property is bound to the Buttons collection.
The default panel used for this element would be a vertically-oriented StackPanel, so I've overridden that with a horizontally-oriented one, to achieve the same layout used in the previous examples.
I've declared an instance of my IValueConverter implementation as a resource so that it can be used in the template.
I've declared a DataTemplate as a resource, with the DataType set to the type of the ButtonViewModel. When presenting the individual ButtonViewModel objects, WPF will look in the in-scope resources for a template assigned to that type, and since I've declared one here, it will use that to present the view model object. For each ButtonViewModel object, WPF will create an instance of the content in the DataTemplate element, and will set the DataContext for the root object of that content to the view model object. And finally,
In the template, the binding uses the converter I declared earlier. This allows me to insert a little bit of C# code into the property binding, to allow me to ensure the string value is handled appropriately, i.e. when it's empty the appropriate DependencyProperty.UnsetValue is used, avoiding any runtime exceptions from the binding engine.
Here's that converter:
class ColorStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string)value;
return !string.IsNullOrWhiteSpace(text) ? text : DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In this case, the ConvertBack() method is not implemented, because we'll only ever be using the binding in the OneWay mode. We just need to check the string value, and if it's null or empty (or whitespace), we return the DependencyProperty.UnsetValue instead.
Some other notes on this implementation:
The Settings.Colors property is set to type System.Collections.Specialized.StringCollection, and initialized (in the Designer) with three empty string values. The length of this collection determines how many buttons are created. You can, of course, use whatever mechanism you want to track this side of the data if you prefer something else.
With 240 buttons, simply arranging them in a horizontal row may or may not work for you (depending on how large the buttons really will be). You can use other panel objects for the ItemsPanel property; likely candidates include UniformGrid or ListView (with the GridView view), both of which can arrange the elements in an automatically spaced grid.
Since the Button elements are located in some kind of parent Panel, such as for example a StackPanel, you could iterate through its Children collection like this:
foreach(Button button in thePanel.Children.OfType<Button>())
{
//...
}
XAML:
<StackPanel x:Name="thePanel">
<Button x:Name="Button0" HorizontalAlignment="Left" Margin="197,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color0, Mode=TwoWay}" Click="Button0_Click" />
<Button x:Name="Button1" HorizontalAlignment="Left" Margin="131,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color1, Mode=TwoWay}" Click="Button1_Click" />
<Button x:Name="Button0_Copy" HorizontalAlignment="Left" Margin="563,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Color_0, Mode=TwoWay, Source={x:Static properties:Settings.Default}}" Click="Button0_Copy_Click"/>
<Button x:Name="Button1_Copy" HorizontalAlignment="Left" Margin="497,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Color_1, Mode=TwoWay, Source={x:Static properties:Settings.Default}}" Click="Button1_Copy_Click"/>
</StackPanel>
The same questions has been asked many times on this site and I have read most of them. But I have a special problem (maybe?) that couldn't figure it out after hours of struggling and reading SO posts.
The problem is -simply explained, I have a WPF form which contains a Connect button. If this button is pressed a textblock must appear on that form, displaying the word "Connecting...". Upon pressing the button, some handshaking operations are done in the associated C# code which takes some time. If the program fails to connect, the textblock must change to "Failed!". Otherwise, it changes to "Succeed."
Now for this simple problem, I wrote in my XAML:
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button x:Name="connecting" Content="Connect" FontWeight="Bold" Click="startConnection"
Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0"/>
<TextBlock x:Name="comm_stat" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Content}"/>
</Grid>
</Window>
And the C# code (inspired by this answer):
using System;
using System.Text;
using System.Windows;
using System.ComponentModel;
namespace WpfTest
{
public class DynamicObj : INotifyPropertyChanged
{
public DynamicObj() : this(string.Empty) { }
public DynamicObj(string txt) { Content = txt; }
private string _name;
public string Content
{
get { return _name; }
set {
_name = value;
OnPropertyChanged("Content");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
comm_stat.DataContext = new DynamicObj();
}
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
bool connect2device = false;
// do the handshaking operations. the result is then assigned to connect2device
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
// some other operations
}
}
}
Now the problem is, whenever I click the button, no text is appeared in the textblock. Because the program waits for the startConnection method to reach its end and then updates the bonded textblock. But I want the textblock to change right after pressing the button. How can I do this?
You can use BackgroundWorker as such:
bool connect2device = false;
private void startConnection(object sender, RoutedEventArgs e)
{
comm_stat.Text = "Connecting...";
// do the handshaking operations. the result is then assigned to connect2device
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += Completed;
worker.RunWorkerAsync();
}
private void Completed(object sender, RunWorkerCompletedEventArgs e)
{
comm_stat.Text = connect2device ? "Succeed." : "Failed!";
}
private void DoWork(object sender, DoWorkEventArgs e)
{
//Change with actual work.
Thread.Sleep(1000);
connect2device = true;
}
One side note is that you actually do not use bindings to change the text. comm_stat.Text = "Connecting..."; sets the text property directly and the DynamicObj object is not used at all. It might be good for you to read a few tutorial on MVVM.
I got an Problem with updating the text in a Textbox. I got this MainWindow:
<Window x:Class="TestDatabinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,10"/>
<Button Grid.Row="1" Content="Click me" Margin="10,10,10,10" Click="Button_Click"></Button>
<Button Grid.Row="2" x:Name="a1" Content="ShowText" Margin="10,10,10,10" Click="a1_Click" ></Button>
</Grid>
Now the cs-file for this MainWindow looks like:
using System.Windows;
namespace TestDatabinding
{
public partial class MainWindow : Window
{
MainWindowViewModel mwvm;
public MainWindow()
{
InitializeComponent();
mwvm = new MainWindowViewModel();
this.DataContext = mwvm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
mwvm.ChangeText();
this.DataContext = mwvm;
}
private void a1_Click(object sender, RoutedEventArgs e)
{
mwvm.showText();
}
}
}
And last but not least the ViewModel Class:
using System.ComponentModel;
using System.Windows;
namespace TestDatabinding
{
class MainWindowViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string text;
public string Text
{
get { return this.text; }
set
{
this.text = value;
OnPropertyChanged("Text");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void ChangeText()
{
this.Text = "Hey paadddyy";
}
public void showText()
{
MessageBox.Show(Text);
}
}
}
I didn´t implement ICommands, because this is a simple test.
Now the Button's work correctly but the Textbox Text didn´t get updated.
Any suggestions what i can do? I only want to display "Hey paadddyy" when I click the first Button. After I press the second Button and then the first the MessageBox shows "Hey paadddyy" but the Textbox text didn´t get updated :(
Thank you for every hint ;)
Your MainWindowViewModel does not implement INotifyPropertyChanged. It needs to look like that:
class MainWindowViewModel: INotifyPropertyChanged
you define the event but does not implement the interface
It need to implement INotifyPropertyChanged
I suggested that if you want to do something with Notify Property. Another easy way is to apply Caliburn.Micro Framework to your project.
Follow this link.