I'm creating a Windows 8 app, and I'm struggling the last couple of days with a custom user control. I can't really figure out whats wrong.
The weird thing is that the dependencyproperty calls the propertychanged event when I change Source in code, but with the binding its not updating.
So here's my code:
GamePage.xaml.cs
public sealed partial class GamePage
{
GamePageViewModel viewModel;
public GamePage()
{
this.InitializeComponent();
viewModel = new GamePageViewModel();
}
}
GamePage.xaml
<common:LayoutAwarePage x:Class="WordSearcher.GamePage"
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:common="using:WordSearcher.Common"
xmlns:controls="using:WordSearcher.Controls"
xmlns:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding GamePageViewModel, Source={StaticResource Locator}}">
<StackPanel Orientation="Horizontal">
<StackPanel.Background>
<ImageBrush ImageSource="Assets/Wood.jpg" Stretch="UniformToFill"/>
</StackPanel.Background>
<controls:PuzzleControl Source="{Binding Path=PuzzleData}"/>
</StackPanel>
</common:LayoutAwarePage>
GamePageViewModel.cs
public class GamePageViewModel : ViewModelBase
{
private List<string> _puzzleData;
public List<string> PuzzleData
{
get
{
return _puzzleData;
}
set
{
this._puzzleData = value;
RaisePropertyChanged("PuzzleData");
}
}
public GamePageViewModel()
{
SetNewData();
}
private async void SetNewData()
{
await SomeManager.Prepare();
PuzzleData = SomeManager.Create(20);
}
}
PuzzleControl.xaml.cs
<UserControl
x:Class="WordSearcher.Controls.PuzzleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WordSearcher.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="500"
d:DesignWidth="500">
<Grid x:Name="puzzleGrid"
Width="500"
Height="500"
>
</Grid>
</UserControl>
PuzzleControl.xaml.cs
public sealed partial class PuzzleControl : UserControl
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(ObservableCollection<string>), typeof(PuzzleControl), new PropertyMetadata(null, PropertyChanged));
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//
// not called from binding
//
((PuzzleControl)d).OnItemsSourcePropertyChanged(e);
}
private void OnItemsSourcePropertyChanged(DependencyPropertyChangedEventArgs e)
{
Source = (ObservableCollection<string>)e.NewValue;
SetGridData();
}
public ObservableCollection<string> Source
{
get
{
return (ObservableCollection<string>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public PuzzleControl()
{
this.InitializeComponent();
CreateRowsAndColumns();
}
private void CreateRowsAndColumns()
{
//create rows and columns in puzzleGrid
//works fine
}
private void SetGridData()
{
//fill puzzleGrid with data
//works fine
}
}
Does anyone knows with is wrong in my code? Because when I put Source = new ObservableCollection(); in the constructor of PuzzleData, the PropertyChanged event will raise. Is it anything with the DataContext?
Thnx in advance!
I don't know for sure,
but you set <controls:PuzzleControl Source="{Binding Path=PuzzleData}"/>
PuzzleData = List<string>
and
Source = ObservableCollection<string>
If the binding even works the first time (what it apperantly does) then it might be the case the source is set to List<string> in some way instead of ObservableCollection<string>. That might be the case why your dependencyproperty method (PropertyChanged) is not called because it is registered to ObservableCollection<string>.
But this is all pure speculation, haven't tested it.
After I got his code an reviewed it I found out that the PuzzleData was never really set and that that was the error... False Alarm....
Are you sure binding context? And How binding object? If you use your user control like in a gridview, DataContext is changed, and differend Datacontext of root page.
<controls:PuzzleControl Source="{Binding Path=DataContext.PuzzleData}"/>
if you use sub control, your user control; bind ElementName property like this:
<controls:PuzzleControl Source="{Binding Path=DataContext.PuzzleData, ElementName=pageRoot}"/>
If you not sure, tracing DataContext binding values on debug via breakpoints.
Related
I am using [RelayCommand] for handling the navigated event of WebView in .NET MAUI.
async void Navigated(WebNavigatedEventArgs args)
I have bound it in my XAML, using
<toolkit:EventToCommandBehavior
EventName="Navigated"
Command="{Binding NavigatedCommand}" />
The command is firing, the arguments are null.
Am I missing something?
Edit: I want to clarify this a bit.
The command is called correctly, when the event is raised.
The arguments in that command (WebNavigatedEventArgs args) are null.
If this is used as event in the page, there isn't any problem.
The arguments hold the response and it is correct.
I am using CommunityToolkit.Maui Version 1.2.0.
And CommunityToolkit.MVVM Version 8.0.0.
Edit2: After testing on another machine, the same code runs correctly and the arguments are passed to the command. (Dependency 6.0.400)
After updating it to 6.0.486 as well, the command parameters became null.
the arguments are null
What does this mean? I have done a sample and it worked well.
The xaml:
<VerticalStackLayout>
<WebView HeightRequest="700" Source="">
<WebView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Navigated"
Command="{Binding IncrementCounterCommand}"/>
</WebView.Behaviors>
</WebView>
<Label Text="{Binding Counter}" HeightRequest="50" BackgroundColor="Red" TextColor="Green" FontSize="Large"/>
</VerticalStackLayout>
The viewmodel:
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
private int counter;
public int Counter
{
get => counter;
private set => SetProperty(ref counter, value);
}
public ICommand IncrementCounterCommand { get; }
private void IncrementCounter() => Counter++;
}
Or you want to add a navigated event in the page.cs, you can try:
In the xaml:
<VerticalStackLayout>
<WebView Navigated="WebView_Navigated" HeightRequest="700" Source=""/>
<Label Text="{Binding Counter}" HeightRequest="50" BackgroundColor="Red" TextColor="Green" FontSize="Large"/>
</VerticalStackLayout>
In the page.cs:
private void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
//do something
}
In addition, I also tried use them together and found that the navigeted event will execute at first, then the command will execute after it.
Update how to pass the WebNavigatedEventArgs into the view model
In the view model:
public class MyViewModel : ObservableObject
{
public WebNavigatedEventArgs webNavigatedEventArgs;
public MyViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
public ICommand IncrementCounterCommand { get; }
private void IncrementCounter() => Navigated(webNavigatedEventArgs);
private async void Navigated(WebNavigatedEventArgs arg)
{
}
}
In the page.cs:
private void WebView_Navigated(object sender, WebNavigatedEventArgs e)
{
var viewmodel = (MyViewModel)this.BindingContext;
viewmodel.webNavigatedEventArgs = e;
}
In the xaml:
<WebView Navigated="WebView_Navigated" HeightRequest="700" Source=">
<WebView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="Navigated"
Command="{Binding IncrementCounterCommand}"/>
</WebView.Behaviors>
</WebView>
But I really don't suggest you do so.
I'm creating app in UWP and i have question.
Can I somehow connection MVVM Light with SelectionChanged event (e.g. ListView) or with other event?
I would like that when I will click on some Item in ListView then I call SelectionChanged.
How do I do?
You can write the Method in ViewModel ,and use the x:bind to connection ViewModel.
The MVVMLight's method is use in WPF that cant bind the event in Method.
UWP can use x:bind to bind the UI event to ViewModel.
The sample:
XAML:
<ListView SelectionChanged = "{x:bind view.SelectionChanged }"/>
XAML.cs:
private ViewModel View{set;get;}
ViewModel:
public void SelectionChanged()
{
}
You can use ItemClick event that will run when you click the ListViewItem .
Inside your viewmodel something *.cs
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
this._canExecute = canExecute;
this._execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
public class MyViewModel
{
private ICommand _doSelectionChangedCommand;
public ICommand DoSelectionChangedCommand
{
get
{
if (_doSelectionChangedCommand == null)
{
_doSelectionChangedCommand = new RelayCommand(
p => this.CanSelectionChanged,
p => this.DoSomeImportantMethod());
}
return _doSomething;
}
}
}
In your viewSomemthing.xaml
--For namespace
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
--Then go down to your control, we'll use Combobox as an example
<ComboBox ... />
<i:Interaction.Triggers>
<EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding DoSelectionChangedCommand}"/>
</EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
I am developing a Windows Universal App which is hosting a web app using webview.
steps are followed as like.
Creating a Blank universal window app. Creating a Splash screen. Set
splash screen as starting page. After all activity i would like to
navigate the Main page which is having a web view control.
Setting a url example "http:www.google.come" as source for the web view. everything it works a fine but the main page takes time, where i would like to see the same splash screen till it loads.
Code for Navigation i am using
this.Frame.Navigate(typeof(MainPage));
full source code
public sealed partial class ExtentedSpash : Page
{
public ProgressMessage Progress;
public ExtentedSpash()
{
this.InitializeComponent();
Progress = ProgressMessage.GetMessage();
DataContext = Progress;
Window.Current.Activate();
Loaded += Splash_Loaded;
}
private async void Splash_Loaded(object sender, RoutedEventArgs e)
{
await Initialize();
Window.Current.Activate();
await ClearBrowserCache();
Window.Current.Activate();
//Task.WaitAll(TaskList.ToArray());
await StartApplication();
}
public async Task Initialize()
{
Progress.ActionMessage = "Initialize the controls";
await Task.Delay(TimeSpan.FromSeconds(10));
}
public async Task ClearBrowserCache()
{
Progress.ActionMessage = "Clear Browser Cache";
await Task.Delay(TimeSpan.FromSeconds(10));
}
public async Task StartApplication()
{
Progress.ActionMessage = "Loading";
await Task.Delay(TimeSpan.FromSeconds(10));
this.Frame.Navigate(typeof(MainPage));
}
private void btnMain_Click(object sender, RoutedEventArgs e)
{
}
}
public class ProgressMessage : INotifyPropertyChanged
{
private string statusMessage;
public string StatusMessage
{
get { return statusMessage; }
set
{
statusMessage = value;
RaiseProperChanged();
}
}
private string actionMessage;
public string ActionMessage
{
get { return actionMessage; }
set
{
actionMessage = value;
RaiseProperChanged();
}
}
private bool showProgress;
public bool ShowProgress
{
get { return showProgress; }
set { showProgress = value;
RaiseProperChanged();
}
}
public static ProgressMessage GetMessage()
{
var msg = new ProgressMessage()
{
StatusMessage = "Initializing Application",
ActionMessage = "One moment please...",
showProgress = true
};
return msg;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseProperChanged(
[CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
}
I want "On Loading" message should show til it fully loads the application.
As we've discussed, if you just want to cover the WebView when it's source is not complete loaded, you can do it like this:
<Page.Resources>
<Storyboard x:Key="MyTextSTD" x:Name="MyTextSTD" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="tbbrush" Storyboard.TargetProperty="Color" Duration="0:0:10">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="Red" />
<LinearColorKeyFrame KeyTime="0:0:5" Value="Blue" />
<LinearColorKeyFrame KeyTime="0:0:10" Value="Purple" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<WebView Source="https://msdn.microsoft.com/library/windows/apps/xaml/mt244352.aspx" NavigationCompleted="WebView_NavigationCompleted">
</WebView>
<Grid x:Name="loadingGrid" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Visibility="Visible">
<TextBlock Text="On Loading..." FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Foreground>
<SolidColorBrush x:Name="tbbrush" />
</TextBlock.Foreground>
</TextBlock>
</Grid>
</Grid>
And code behind:
public MainPage()
{
this.InitializeComponent();
MyTextSTD.Begin();
}
private void WebView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
loadingGrid.Visibility = Visibility.Collapsed;
}
Here is quite simple, I use a TextBlock and some color animation to show the message. And this message will disappear when the source of WebView is fully loaded.
i have an combox control defined with events in my mainpage.xaml
<Grid x:Name="LayoutRoot">
<ComboBox SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
</Grid>
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
now how do we defined events for combox control in mvvm model .
and how do we bind the collection list to combo box. i am using SL 3
thanks
prince
In your xaml, you can bind the ItemSource and SelectedItem as shown below:
MainPage.xaml
<UserControl x:Class="App1.MainPage"
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:App1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<local:MainPage_ViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionChanged="ComboBox_SelectionChanged" Height="30" Width="100"/>
</Grid>
In the MainPage.xaml.cs, your Selection changed method could just call the method on your ViewModel since you are using SL3:
MainPage.xaml.cs
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private MainPage_ViewModel viewModel
{
get { return this.DataContext as MainPage_ViewModel; }
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.viewModel.SelectionChanged();
}
}
Your ViewModel would have the MyItems collection and the SelectedItem to bind to:
MainPage_ViewModel.cs
public class MainPage_ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> MyItems
{
get { return myItems; }
set { myItems = value; }
}
private ObservableCollection<string> myItems = new ObservableCollection<string>() { "One", "Two", "Three" };
public string SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; }
}
private string selectedItem = string.Empty;
public void SelectionChanged()
{
//Perform logic that needs to happen when selection changes
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Depending on what you were using your SelectionChanged method for, you may no longer need it since this would bind the SelectedItem to the ViewModel.
Hope this helps!
I have a data class that implements INotifyPropertyChanged and two WPF controls that DataBind to the same value. Only one WPF control is updated, why?
Here is my data class:
using System;
using System.ComponentModel;
using System.Windows.Threading;
namespace TestMultiBind
{
class DataSource : INotifyPropertyChanged
{
static int _DataValue;
static DispatcherTimer tmr = new DispatcherTimer();
static int _ClientCount = 0;
#region InotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public int DataValue
{
get { return _DataValue; }
}
public DataSource()
{
if (!DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
{
_ClientCount = _ClientCount + 1;
if (!tmr.IsEnabled)
{
tmr.Interval = TimeSpan.FromMilliseconds(10);
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();
}
}
}
void tmr_Tick(object sender, EventArgs e)
{
_DataValue = DateTime.Now.Second;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DataValue"));
}
}
}
}
and here is my XAML
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<ProgressBar Height="20" Name="progressBar1" Value="{Binding Mode=OneWay, Path=DataValue}" Maximum="60">
<ProgressBar.DataContext>
<ds:DataSource/>
</ProgressBar.DataContext>
</ProgressBar>
<ProgressBar Height="30" Name="progressBar2" Value="{Binding Mode=OneWay, Path=DataValue}" Maximum="60">
<ProgressBar.DataContext>
<ds:DataSource/>
</ProgressBar.DataContext>
</ProgressBar>
</StackPanel>
</Grid>
I have tried to present the simplest example possible, thus I created a data class that uses a timer to update a value once per second. The real world problem I am trying to solve is data coming across a serial port that is to be displayed. Thus I need to store static data for the incoming data within the class as well as handling instance specific events for each of the clients (WPF controls) that are data bound to the control.
It would appear that my tmr_Tick routine is somehow instance specific to the first client as opposed to being static to the class and raising the multiple events required for each of the clients.
What am I missing here or doing wrong?
I'm not sure if this is the best way to do this, but it works. The problem with my initial code was that I needed both a static and a instance constructor for the class. The timer is created in the static constructor (which is only run once) while there needs to be one event handler per binding so this is handled in the instance constructor.
Here is the code that works:
using System;
using System.ComponentModel;
using System.Windows.Threading;
namespace TestMultiBind
{
class DataSource : INotifyPropertyChanged
{
static int _DataValue;
static DispatcherTimer tmr = new DispatcherTimer();
#region InotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public int DataValue
{
get { return _DataValue; }
}
static DataSource()
{
if (!DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
{
tmr.Interval = TimeSpan.FromMilliseconds(10);
tmr.Start();
}
}
public DataSource()
{
if (!DesignerProperties.GetIsInDesignMode(new System.Windows.DependencyObject()))
{
tmr.Tick += new EventHandler(tmr_Tick);
}
}
void tmr_Tick(object sender, EventArgs e)
{
_DataValue = DateTime.Now.Second;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("DataValue"));
}
}
}
}