I'm currently working with sliders in WPF. My GUI window has 2 sliders that are supposed to act together in a few ways. slider1 must always be less than or equal to slider2, and slider2 must always be greater than or equal to slider1. My first attempt at using C# code-behind to solve this problem is documented in my previous question. This question got my code to compile, but did not effect any visual change in my program during run time. What would be the ideal method to making these sliders run in the way that I need them to?
Thank you.
Lets say that your ViewModel have 2 properties Slider1 and Slider2 and your XAML looks something like this:
<Slider Value="{Binding Path=Slider1}"/>
<Slider Value="{Binding Path=Slider2}"/>
then you can do your logic in ViewModel when Slider1 or Slider2 is changed:
public class MyClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private double _slider1;
public double Slider1
{
get { return _slider1; }
set
{
if (_slider1 != value)
{
_slider1 = value;
OnPropertyChanged("Slider1");
if (_slider1 > Slider2) Slider2 = _slider1;
}
}
}
private double _slider2;
public double Slider2
{
get { return _slider2; }
set
{
if (_slider2 != value)
{
_slider2 = value;
OnPropertyChanged("Slider2");
if (_slider2 < Slider1) Slider1 = _slider2;
}
}
}
}
for your ease you ca do this also..
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (slider1 == null || slider2 == null)
return;
if (slider1.Value >= slider2.Value)
{
slider2.Value = slider1.Value;
}
}
private void slider2_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (slider1 == null || slider2 == null)
return;
if (slider2.Value <= slider1.Value)
{
slider1.Value = slider2.Value;
}
}
Related
I am using this code following code taken from Stackoverflow.
I want to transfer a string from one view model to another one on SelectionChanged event. But when I click on Tab2, I get Tab2 message box, but when I click on Tab1, I get both the message boxes indicating that both are getting executed. The same when I click Tab1, both message boxes are seen.
MainView.xaml
<TabControl>
<TabItem Header="My tab 1" Selector.IsSelected="{Binding IsMyTab1Selected}"> ... </TabItem>
<TabItem Header="My tab 2" Selector.IsSelected="{Binding IsMyTab2Selected}"> ... </TabItem>
</TabControl>
MainViewModel.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel() {
PropertyChanged += handlePropertyChanged;
}
public bool IsMyTab1Selected {
get { return _IsMyTab1Selected ; }
set {
if (value != _IsMyTab1Selected ) {
_IsMyTab1Selected = value;
OnPropertyChanged("IsMyTab1Selected ");
}
}
}
private bool _IsMyTab1Selected = false;
public bool IsMyTab2Selected {
get { return _IsMyTab2Selected ; }
set {
if (value != _IsMyTab2Selected ) {
_IsMyTab2Selected = value;
OnPropertyChanged("IsMyTab2Selected ");
}
}
}
private bool _IsMyTab2Selected = false;
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "IsMyTab1Selected") {
MessageBox.Show("Tab_1 Clicked!");
} else if (e.PropertyName == "IsMyTab2Selected") {
MessageBox.Show("Tab_2 Clicked!");
}
}
I am not able to get the mutually exclusiveness, point me where I am wrong.
Option 1
you can change the setters to only call OnPropertyChanged(..) when the value is true:
public bool IsMyTab1Selected
{
get { return _IsMyTab1Selected; }
set
{
if (value != _IsMyTab1Selected)
{
_IsMyTab1Selected = value;
if (_IsMyTab1Selected)
OnPropertyChanged("IsMyTab1Selected");
}
}
}
public bool IsMyTab2Selected
{
get { return _IsMyTab2Selected; }
set
{
if (value != _IsMyTab2Selected)
{
_IsMyTab2Selected = value;
if(_IsMyTab2Selected)
OnPropertyChanged("IsMyTab2Selected");
}
}
}
Option 2
Or you can check in your handlePropertyChange() if the value is true like this
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsMyTab1Selected")
{
if(IsMyTab1Selected)
MessageBox.Show("Tab_1 Clicked!");
}
else if (e.PropertyName == "IsMyTab2Selected")
{
if(IsMyTab2Selected)
MessageBox.Show("Tab_2 Clicked!");
}
}
The bindings will update on deselection too. You need to check e.NewValue in your handler, or why not simply check _IsMyTab1Selected etc?
I know this is terribly common issue, but I just can't get the button to update to "Pressed1" and "Pressed2" content when changing "Default" of buttonContent. Having looked at few questions, I can't find the answer that'd work for me, I simply can't find out what is wrong here, so here's the crappy code:
The window with a button
public partial class MainWindow : Window
{
Code_Behind cB;
public MainWindow()
{
cB = new Code_Behind();
this.DataContext = cB;
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
cB.buttonPressed();
}
}
And here's the separate class
public class Code_Behind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _buttonContent = "Default";
public string buttonContent
{
get { return _buttonContent; }
set {
if (_buttonContent != value)
{
buttonContent = value;
OnPropertyChanged("buttonContent");
}
}
}
public void buttonPressed()
{
int timesPressed = 0;
if (timesPressed != 1)
{
_buttonContent = "Pressed1";
timesPressed++;
}
else if (timesPressed != 2)
{
_buttonContent = "Pressed2";
timesPressed++;
timesPressed = 0;
}
}
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
You are not setting the property, but the backing field. Hence the PropertyChanged event is not fired.
Replace
_buttonContent = "Pressed1";
...
_buttonContent = "Pressed2";
with
buttonContent = "Pressed1";
...
buttonContent = "Pressed2";
Besides that, it is a widely accepted convention to write property names with Pascal casing, i.e. ButtonContent instead of buttonContent.
Moreover, your property setter looks odd (probably because you try to squeeze too much code in one line).
Instead of
set
{
if (_buttonContent != value)
{
_buttonContent = value;
}
OnPropertyChanged("buttonContent");
}
it should certainly be
set
{
if (_buttonContent != value)
{
_buttonContent = value;
OnPropertyChanged("buttonContent");
}
}
When trying to bind a ListView to an ObservableCollection via XAML, the ListView is not updated and is initially loaded with blank values.
Via XAML
History.xaml.cs
DataContext = this;
History.xaml:
<ListView x:Name="lvHistory" ItemsSource="{Binding Source=history}" BorderThickness="0" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="2" util:GridViewSort.AutoSort="True" SizeChanged="lvHistory_SizeChanged">
Via CODE
When doing the binding via code, the bindings work correctly.
History.xaml
<ListView x:Name="lvHistory" BorderThickness="0" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="2" util:GridViewSort.AutoSort="True" SizeChanged="lvHistory_SizeChanged">
History.xaml.cs
DataContext = this;
lvHistory.ItemsSource = history;
By simply adding the ItemsSource via code and removing it in XAML, the code works properly. What am I missing? How do I create the bindings via pure XAML?
history:
public ObservableCollection<LocateElement> history { get; private set; }
Code for updating the list:
public void Update()
{
if (updater.IsBusy) updatePending = true;
else
{
searchValue = txtSearch.Text.Trim();
updatePending = false;
updater.RunWorkerAsync();
}
}
private void updateContent(object sender, DoWorkEventArgs e)
{
try
{
Globals.Variables.logger.Info("Locate History: Updating");
using (var db = new Data.DataManager())
{
var history = db.LocateHistory.Where(o => o.ReceivedBy == Globals.Variables.loginDetails.UserID);
e.Result = filterResults(history);
}
}
catch (Exception er)
{
Globals.Variables.logger.Error(er);
}
}
private void updateFinished(object sender, RunWorkerCompletedEventArgs e)
{
List<LocateElement> r = (List<LocateElement>)e.Result;
history.Clear();
foreach (LocateElement l in r)
{
history.Add(l);
}
if (updatePending) Update();
//else Wpf.Util.GridViewSort.ReapplySort(lvHistory);
}
private List<LocateElement> filterResults(IQueryable<LocateElement> list)
{
List<LocateElement> history = new List<LocateElement>();
foreach (LocateElement l in list)
{
if (searchValue != "")
{
// Use the parameters to filter the results.
Regex reg = new Regex(WildcardToRegex(searchValue));
if (reg.IsMatch(l.Serial) || reg.IsMatch(l.Asset) || reg.IsMatch(l.DeviceType) || reg.IsMatch(l.Company) || (l.ReceivedFrom != null && reg.IsMatch(l.ReceivedFrom.Name)) || (l.ReceivedTo != null && reg.IsMatch(l.ReceivedTo.Name)) || reg.IsMatch(l.Row) || reg.IsMatch(l.Shelf) || reg.IsMatch(l.Bin) || reg.IsMatch(l.DateReceived.ToString()))
{
history.Add(l);
}
}
else
{
history.Add(l);
}
}
return history;
}
When you assign data to your history collection you need to make sure you raise the property changed event.
For example:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<LocateElement> _history;
public ObservableCollection<LocateElement> history
{
get { return _history; }
set
{
if (_history != value)
{
_history = value;
RaisePropertyChanged("history");
}
}
}
public MyViewModel()
{
_history = new ObservableCollection<LocateElement>();
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The Source property of a Binding doesn't mean what you think it means. Use Path instead or let it assume you're talking about Path (default). This should do it.
<ListView ItemsSource="{Binding history}" ...>
Additionally, if you're setting the history property outside your constructor it needs to notify of property changed. IF you're only setting it in your constructor you won't need to but you might want to make it backed by a readonly field instead of an auto getter/setter. (TrueEddie's solution describes this problem and supplies the solution for being able to swap out the actual variable).
I'm trying to bind some XAML code to a property in my ViewModel.
<Grid Visibility="{Binding HasMovies, Converter={StaticResources VisibilityConverter}}">
...
</Grid>
My ViewModel is setup like this:
private bool _hasMovies;
public bool HasMovies
{
get { return _hasMovies; }
set { _hasMovies = value; RaisePropertyChanged("HasMovies"); }
}
In the constructor of the ViewModel, I set the HasMovies link:
MovieListViewModel()
{
HasMovies = CP.Connection.HasMovies;
}
in CP:
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
private ObservableCollection<Movie> _movies;
public ObservableCollection<Movie> MovieList
{
get { return _movies; }
set
{
_movies = value;
RaisePropertyChanged("MovieList");
RaisePropertyChanged("HasMovies");
_movies.CollectionChanged += MovieListChanged;
}
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
What am I doing wrong? How should I change this binding so that it reflects the current state of CP.Connection.HasMovies?
Either directly expose the object in the ViewModel and bind directly through that (so that the value is not just copied once which is what happens now) or subscribe to the PropertyChanged event and set HasMovies to the new value every time it changes in your source object.
e.g.
CP.Connection.PropertyChanged += (s,e) =>
{
if (e.PropertyName = "HasMovies") this.HasMovies = CP.Connection.HasMovies;
};
First of all, the setter for a collection type, such as your MovieList property, is not called when you change the content of the collection (ie. Add/Remove items).
This means all your setter code for the MovieList property is pointless.
Secondly, it's very silly code. A much better solution, is to use NotifyPropertyWeaver. Then your code would look like this, in the viewmodel:
[DependsOn("MovieList")]
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
Alternatively you would have to add a listener for the CollectionChanged event when you initialize the MovieList property the first time (no reason to have a backing property, really really no reason!), and then call RaisePropertyChanged("HasMovies") in the event handler.
Example:
public class CP : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CP()
{
MovieList = new ObservableCollection<Movie>();
MovieList.CollectionChanged += MovieListChanged;
}
public bool HasMovies
{
get { return MovieList != null && MovieList.Count > 0; }
}
public ObservableCollection<Movie> MovieList
{
get;
private set;
}
private void MovieListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("HasMovies");
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
the Text property of control on winform is always string type, so if i wanna expose property of other type for custom control, i have to do the conversion as following, if i have dozens of properties to expose, it will be such pain for me.
public int ImageGroupLength
{
get
{
return int.Parse(this.imageGroupLength.Text);
}
set
{
this.imageGroupLength.Text = value.ToString();
}
}
so, is there any elegant way to do the conversion?
Creating your own control is the way to go here. Add a new class to your project and paste the code shown below. Compile. The new control shows up on the top of your toolbox. You'll want to implement the BadValue event to warn the user that the entered text isn't suitable. And the ValueChanged is available to get an event when the Value property changes.
using System;
using System.Windows.Forms;
class ValueBox : TextBox {
public event EventHandler BadValue;
public event EventHandler ValueChanged;
private int mValue;
public int Value {
get { return mValue; }
set {
if (value != mValue) {
mValue = value;
OnValueChanged(EventArgs.Empty);
base.Text = mValue.ToString();
}
}
}
protected void OnValueChanged(EventArgs e) {
EventHandler handler = ValueChanged;
if (handler != null) handler(this, e);
}
protected void OnBadValue(EventArgs e) {
EventHandler handler = BadValue;
if (handler != null) handler(this, e);
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
base.Text = mValue.ToString();
}
protected override void OnValidating(System.ComponentModel.CancelEventArgs e) {
int value;
if (!int.TryParse(base.Text, out value)) {
SelectionStart = 0;
SelectionLength = base.Text.Length;
e.Cancel = true;
OnBadValue(EventArgs.Empty);
}
else base.OnValidating(e);
}
}
Have you considered subclassing the TextBox control and simply placing that on your custom control instead? You could create a new property that parses the input string and returns an integer.
Not exactly, but you can at-least get some safety in there by using something like this.
This will save you heart-ache when people try and put text into the length field!
public int ImageGroupLength
{
get
{
int ret;
int.TryParse(this.imageGroupLength.Text, out ret);
return ret; //Ret will be 0 if tryparse fails
}
set
{
...
}
}