OnPropertyChanged method is not firing - c#

In WP8 app, i have few controls where i bind the foreground color which i am changing in the codebehind. But OnPropertyChanged is not firing when the user event happened.
I have defined this binding "ControlForeground" in my textblock and radiobutton data template controls in it. I am trying to change the Foreground color whenever user presses the button. But my new color assignment is not updating the UI. Anything i am missing here?
In XAML,
<TextBlock x:Name="lblTileColor" TextWrapping="Wrap" Text="Selected color:" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
<TextBlock x:Name="lblTileColor2" TextWrapping="Wrap" Text="App bg:" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
<RadioButton x:Name="accentColor" IsChecked="true" BorderBrush="White" Foreground="{Binding ControlForeground, Mode=TwoWay}">
<RadioButton.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle Width="25" Height="25" Fill="{StaticResource PhoneAccentBrush}"/>
<TextBlock Width="10"/>
<TextBlock x:Name="lblDefaultAccent" Text="Default accent color" Foreground="{Binding ControlForeground, Mode=TwoWay}"/>
</StackPanel>
</DataTemplate>
</RadioButton.ContentTemplate>
</RadioButton>
<Button x:name="UpdateColor" click="update_btn"/>
In C#,
public class ColorClass : INotifyPropertyChanged
{
private SolidColorBrush _ControlForeground;
public SolidColorBrush ControlForeground
{
get
{
return _ControlForeground;
}
set
{
_ControlForeground = value;
OnPropertyChanged("ControlForeground");
}
}
public ColorClass() { }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public class ColorPage:PhoneApplicationPage{
public ObservableCollection<ColorClass> TestCollection { get; private set; }
public void update_btn(object sender, EventArgs e){
TestCollection.Add(new ColorClass()
{
ControlForeground = new SolidColorBrush(Colors.Red)
});
}
}

For your 2nd problem (not being able to bind controls inside your data template), this is because these controls will use the data context of the their parent template not the data context of the page.
To fix this, you'll have to tell these controls the element name with the data context and give it full path of your property.
<TextBlock
x:Name="lblDefaultAccent"
Text="Default accent color"
Foreground="{Binding DataContext.ControlForeground,
ElementName=LayoutRoot, Mode=TwoWay}"/>
As you can see above you have to specify the element name. In case you bound this using this.DataContext = colorClass then the element name will be the name of the outer grid in your xaml, defaulted as LayoutRoot

You can only bind an ObservableCollection to controls which expect it, like a ListBox or LongListSelector. Additionally, adding a Brush to the TestCollection doesn't fire the non-functional notification since it doesn't call the setter of that property, just modifies the existing object.
Make TestCollection a type ColorClass and change the .Add stuff to just change the ColorClass.ControlForeground property and this should "just work."

Related

WPF - Checkbox Command not firing

I am writing a WPF app using the MVVM pattern and I am having the following problem: I have bound a command to a checkbox in my UI however my event handler is not being called when the check box is clicked. I have used the same approach to bind other UI elements such as buttons and it seems to work alright for them. The relevant xaml is as follows:
<ListBox ItemsSource="{Binding ElementsMethods}" Height="auto" x:Name="MethodsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FormattedEM}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Started"/>
<Checkbox IsChecked="{Binding Started} Command="{Binding elementMethodCheckboxChangeCommand}"> </CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finished"/>
<CheckBox IsChecked="{Binding Finished}"></CheckBox>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>IsChecked="{Binding Finished}
Where elementMethodCheckboxChangeCommand is a public property of type ICommand in my viewmodel class:
public ICommand elementMethodCheckboxChangeCommand { get; set; }
the concrete class used to set this property is named relay command:
elementMethodCheckboxChangeCommand = new RelayCommand(new Action<object>(elementMethodCheckboxChange));
where elementMethodCheckboxChange is a public void function taking a parameter of type object.
The implementation of the relaycommand class is as follows:
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello world");
}
}
public event EventHandler CanExecuteChanged;
}
Like I said above I have used this same approach to bind to buttons in my UI and they have worked as expected, however when I click the checkbox nothing happens at all, and my event handler is not executed.
I hope someone can help me out here as this problem is starting to become really frustrating - please ask if you need any additional information. Thank you all in advance :)
You should specify a RelativeSource of the binding when you want to bind to a property of the view model inside an `ItemTemplate:
<CheckBox ... Command="{Binding DataContext.elementMethodCheckboxChangeCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"/>
The default DataContext is the current item in the ItemsSource and this one has no elementMethodCheckboxChangeCommand property to bind to.
Making the property static is not a very good solution.

Getting 'IsMouseOver' to work on a ListView when ItemsSource is set to a list

I have a WPF project where I have created a UserControl for the purpose of making a custom ListViewItem which includes a close button inside. Here is the code for that:
<ListViewItem x:Name="lviTab" Height="36" Background="#232323"
MouseUp="LviTab_MouseUp" MouseEnter="LviTab_MouseEnter">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TabText}" FontSize="15" />
<ListViewItem x:Name="lviTabClose" Margin="5, 0, 0, 0"
Padding="0, 0, 0, 0" MouseUp="LviTabClose_MouseUp">
<materialDesign:PackIcon Kind="Close" Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center" Width="20" Height="20" />
</ListViewItem>
</StackPanel>
</ListViewItem>
I am adjusting the text inside of each item by binding the text of the TextBlock inside of the UserControl to another class TabData.cs
public class TabData : UIElement
{
public TabData() : base()
{
}
public string TabText { get; set; }
}
In my ListView, I have set the DataTemplate to my UserControl. I have set the ListView ItemSource to a list of TabData objects.
tabs = new List<TabData>()
{
new TabData{TabText = "Tab 1"},
new TabData{TabText = "Tab 2"},
new TabData{TabText = "Tab 3"},
new TabData{TabText = "Tab 4"},
new TabData{TabText = "Tab 5"}
};
lvTabs.ItemsSource = tabs;
When moving the mouse over a ListViewItem, I need IsMouseOver to be true. I've tried inheriting UIElement to TabData but it hasn't worked. I'm quite new to WPF, I would appreciate any help with figuring out how I can retain IsMouseOver property when using a UserControl and setting the ItemSource to items which arn't ListViewItems.
Here's the simplest way to do what you're trying to do. When you populate a ListView with items from a collection, it creates its own ListViewItems. You don't need to create another ListViewItem inside each of its ListViewItems, much less a third one inside your own.
I'm going to dispense with the UserControl and put that XAML straight in a template. The reason for that is that we want the click handler to be in MainWindow.xaml.cs, where it can interact with the main viewmodel. We could fairly easily make that work with the UserControl in a couple of different ways, but I'm keeping this as simple as I can. Ideally you would use a Command for that, but that's one particular case where a little impurity in your MVVM won't ruin you. And for a case where the item UI is as simple as one textblock and one button, a UserControl is more than you need.
First, MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="TabListViewItemTemplate">
<DockPanel LastChildFill="False">
<TextBlock Text="{Binding TabText}" DockPanel.Dock="Left" />
<Button x:Name="TabCloseButton" Click="TabCloseButton_Click" DockPanel.Dock="Right" >
<Button.Template>
<ControlTemplate TargetType="Button">
<materialDesign:PackIcon
Kind="Close" Foreground="White"
VerticalAlignment="Center" HorizontalAlignment="Center"
Width="20" Height="20" />
</ControlTemplate>
</Button.Template>
</Button>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListView
ItemsSource="{Binding TabItems}"
ItemTemplate="{StaticResource TabListViewItemTemplate}"
HorizontalContentAlignment="Stretch"
/>
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel
{
TabItems = {
new TabData { TabText = "Fred" },
new TabData { TabText = "Ginger" },
new TabData { TabText = "Herman" },
}
};
}
private void TabCloseButton_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement).DataContext is TabData tabData)
{
MessageBox.Show($"TabCloseButton_Click() {tabData.TabText}");
}
}
The TabData class. Don't inherit from UIElement. It's not an element in the user interface, it's just a plain C# class. If you were going to let the user edit TabText, you would make it a viewmodel that implements INotifyPropertyChanged.
public class TabData
{
public string TabText { get; set; }
}
The main viewmodel. Nothing we're doing here actually requires INotifyPropertyChanged on any of your classes, but you'll need it if you turn this into anything useful, so we'll include it.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public class MainViewModel : ViewModelBase
{
public ObservableCollection<TabData> TabItems { get; } = new ObservableCollection<TabData>();
}

Binding of Button isEnabled Property to a Boolean Does Not Change When INotifyPropertyChanged Fires

I have been attempting to successfully bind a Button's isEnabled property to a Boolean variable. After updating the Boolean, the Button's enabled state does not change. I used the helpful documentation from Microsoft for this subject to form my code, found here. Upon searching StackOverflow, I came across this article a regarding similar problem. It did not assist me in fixing this bug.
public class BackState : INotifyPropertyChanged
{
public bool _isEnabled;
public bool isEnabled
{
get
{
return _isEnabled;
}
set
{
if (value != _isEnabled)
{
_isEnabled = value;
NotifyPropertyChanged("isEnabled");
Debug.WriteLine("NotifyPropertyChanged was called successfully");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
Snippet of XAML code:
<Grid DataContext="{Binding BackObject}" HorizontalAlignment="Stretch" Height="250" VerticalAlignment="Top">
<Button x:FieldModifier="public" IsEnabled="{Binding isEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Click="Back_Click" x:Name="Back" Foreground="DarkGray" Content="" FontFamily="Segoe MDL2 Assets" Background="#33FFFFFF" FontSize="20" Margin="40,70,0,133" />
</Grid>
My Boolean Change Function
public static void ChangeState(bool b)
{
BackState bs = new BackState();
if(b == true)
{
bs.isEnabled = true;
Debug.WriteLine("Enabled Property");
}else{
bs.isEnabled = false;
Debug.WriteLine("Disabled Property");
}
}
And here is the BackObject definition
<local:BackState x:Key="BackObject"/>
In the GUI, the button constantly remains enabled. (Regardless of if ChangeState function is called) It should change disable/enable according to the called function.
In your ChangeState method you are creating a new BackState object, whereas this should be a property on the BackObject that you are setting as the DataContext. Instantiate a BackState that is a property on the BackObject, and bind to the isEnabled property of that object so that the xaml is told to listen for changes to that property.
<Grid DataContext="{Binding BackObject}" HorizontalAlignment="Stretch" Height="250" VerticalAlignment="Top">
<Button x:FieldModifier="public" IsEnabled="{Binding yourPropertyWhcihIsBackState.isEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Click="Back_Click" x:Name="Back" Foreground="DarkGray" Content="" FontFamily="Segoe MDL2 Assets" Background="#33FFFFFF" FontSize="20" Margin="40,70,0,133" />
</Grid>
and then in the method:
public static void ChangeState(bool b)
{
if(b == true)
{
yourPropertyWhcihIsBackState.isEnabled = true;
Debug.WriteLine("Enabled Property");
}else{
yourPropertyWhcihIsBackState.isEnabled = false;
Debug.WriteLine("Disabled Property");
}
}
But this won't work if you are ultimately making changes on an instance of an object to which the xaml is not bound. You seem to be creating a new instance of BackObjectin your xaml, but it doesn't look like you have any way of accessing this object in your code. Consider setting up a ViewModel for accomplishing this. Take a look at mvvm light, it's a great mvvm (Model View ViewModel) framework.

Set frame content when a button is clicked using MVVM

I'm new to the MVVM pattern, but I understand some of it. The problem I currently have is that I want to open a page when a button is pressed, using the MVVM pattern. When one of the six buttons is pressed, a command can give me the name of the button that is pressed. My problem is that I don't know how to set the frame's content when the button is pressed.
<StackPanel>
<Button Content="Page 1" x:Name="Page1"
Command="{Binding SimpleCommand, Source={StaticResource ViewModelBase}}"
CommandParameter="{Binding Name, ElementName=Page1}"/>
<Button Content="Page 2" x:Name="Page2"
Command="{Binding SimpleCommand, Source={StaticResource ViewModelBase}}"
CommandParameter="{Binding Name, ElementName=Page2}"/>
</StackPanel>
Above is the XAML code right now. The simplecommand is just to write out the name on the button
<Frame x:Name="MainFrame" Grid.Column="1" Grid.Row="1"
Content="{Binding Name, Converter={StaticResource Converter}, ElementName=Page1}"
NavigationUIVisibility="Hidden"/>
Above is the Frame that i want to change the content. On compile time i can set the page that it should open. I want to set the content on run time, where i use the button name.
The converter is just the IValueConverter, where i set what page it should display.
The way I have approached this was not by using a frame but using a ContentPresenter. You can always insert the ContentPresenter inside of your Frame. Bear in mind that Frame doesn't inherit DataContext so I would avoid using it.
To start of let's create a BaseViewModel be our starting point for views.
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now that we have the base let's create a MainViewModel:
public class MainViewModel : BaseViewModel
{
private BaseViewModel selectedViewModle;
public BaseViewModel SelectedViewModel
{
get { return selectedViewModle; }
set { selectedViewModle = value; OnPropertyChanged(nameof(SelectedViewModel)); }
}
}
At this point our MainViewModel has a property with SelectedViewModel this is what we are going to use for our navigation.
Assumption
I am assuming that you have a working knowledge about commands and how to use them.
Here is a code example of a method for your Navigate command:
void navigate(object parameter)
{
SelectedViewModel = new DetailsViewModel();
}
And here is the code for DetailsViewModel:
public class DetailsViewModel : BaseViewModel
{
//your code with properties and methods here
}
Now let's set up the view:
<UserControl ...>
<Grid>
<ContentPresenter Content="{Binding .}"/>
</Grid>
</UserControl>
Now in the Resources tag for your UserControl include a DataTemplate:
<DataTemplate DataType="{x:Type vm:DetailsViewModel}">
<Grid .../>
</DataTemplate>
At this point you will have the content of the data template presented for you on the screen.

Binding a C# class to XAML for WP7

Ok, so I've been grinding away for hours now and still can't figure out why my data in my ViewModel is not being bound to my XAML in my main page. I even started an new project and implemented it fine the same way so I'm thinking it might have to do with namespaces or something I'm less familiar with.
When my application launches I create a global ViewModel in App.cs which I use to bind data to my XAML view.
public HomeViewModel ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
ViewModel = new HomeViewModel();
(App.Current as App).RootFrame.DataContext = (App.Current as App).ViewModel;
}
Then the HomeViewModel looks something like this:
public class HomeViewModel : INotifyPropertyChanged
{
/***View Model***/
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public HomeViewModel()
{
PropertyChanged = new PropertyChangedEventHandler(delegate { });
}
public Profile CurrentProfile; /*EDIT: Missing {get;set;} Which is necessary for
*any property, including ones below that I
*referenced in the XAML
*/
public string NotificationImage;
public ButtonPanelPath UniversalButtonPath;
public void setProfile(Profile p)
{
CurrentProfile = p;
NotifyPropertyChanged("CurrentProfile");
}
.
.
....rest of access methods and properties
Now when my program runs I am 100% sure that the data in HomeViewModel is getting updated and the NotifyPropertyChanged method is being called every time a new field is "set".
And this class is bound to the RootFrame right? So shouldn't I be able to access these fields in my main page's xaml? This is an example of part of the xaml in a stack panel in the main grid:
<Border BorderThickness="5" BorderBrush="Aqua" CornerRadius="20">
<StackPanel Name="profileInfo" DataContext="{Binding CurrentProfile}">
<TextBlock Text="{Binding FirstName}" Name="profileName" FontSize="26"
FontWeight="Bold" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal">
<StackPanel>
<TextBlock Text="{Binding Level}" Name="userLevel" FontSize="32"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding LevelName}" Name="levelName" FontSize="26"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding PointsNeeded}" Name="pointsBar"
Margin="10,0,0,0"/>
</StackPanel>
<Image x:Name="levelIcon" Source="{Binding PictureUrl}"
Margin="15,0,0,0"/>
</StackPanel>
</StackPanel>
</Border>
So here Level, LevelName, PointsNeeded and PictureUrl are all public fields in Profile (or CurrentProfile which is the specific instance of Profile I'm referencing). I tried Profile.[field] but that didn't work either. If anyone could tell me what I'm missing to complete the binding it would be greatly appreciated.
By the way the namespaces are as follows if that means anything
-MainPage is in MyApp.src.pages
-App is in MyApp
-HomeViewModel is in MyApp.src.classes
Thanks in advance for your helpful solutions/comments, if you'd like more data/info please just ask.
The binding you are looking for is {Binding Proptery.SubProperty}.
So in your case for example {Binding CurrentProfile.Level}.
You are having an instance of your "HomeViewModel" in the DataContext, so you can access all of its propteries. If there is a complex type as a property, you have to access the property, the instance of the complex type not the type, to access its "sub"-properties.
Hope it helps.

Categories