I have a problem and nothing come to my mind how to solve this.
I need to add a button to Favorites in a StackPanel, the problem is that the button is pressed on other page that is StackPanel declared and I can't Add button to it's Children.
So, I have 2 pages: MainPage.xaml and PlayerPage.xaml .
In MainPage I keep a StackPanel with Buttons like:
<StackPanel x:Name="mainStack" >
<Button x:Name="but1" Click="Button2_Click" Tag="/videos/video1.mp4" Content="Play Video 1" />
<Button x:Name="but2" Click="Button2_Click" Tag="/videos/video2.mp4" Content="Play Video 2" />
<Button x:Name="but3" Click="Button2_Click" Tag="/videos/video3.mp4" Content="Play Video 3" />
</StackPanel>
<StackPanel x:Name="favoriteStack" >
<!-- Here need to be added favorite videos when user press Add to fav button! -->
</StackPanel>
.cs
private void Button2_Click(object sender, EventArgs e)
{
NavigationService.Navigate(
new Uri("/PlayerPage.xaml?path=" +
HttpUtility.UrlEncode((sender as Button).Tag.ToString()),
UriKind.Relative));
}
In PlayerPage.xaml :
<MediaElement x:Name="mediaElement1"
MediaOpened="mediaElement1_MediaOpened"
MediaFailed="mediaElement1_MediaFailed"
MediaEnded="mediaElement1_MediaEnded"
CurrentStateChanged="mediaElement1_CurrentStateChanged" />
<Button x:Name="AddToFav" Click="Button1_Click"
Content="Add this video to Favorites" />
.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (NavigationContext.QueryString.TryGetValue("path", out path))
{
if (!string.IsNullOrWhiteSpace(path))
{
mediaElement1.Source = new Uri( path );
}
}
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
// here must be somethink like:
Button butsender = new Button();
butsender = sender as Button;
stack2.Children.Add(butsender);
//better will be to save to Isolated Storage or somethink like this for future launching...
}
I have a lot of problems because I don't really get it how to perform that... I've tried to use global App Bar and allot more but unsuccesfully. Any help would be appreciated! Thanks!
The best way to do this is to follow the MVVM Pattern. You'll have a ViewModel class defined containing two lists.
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<Video> Videos { get; set; }
public ObservableCollection<Video> FavoriteVideos { get; set; }
public MainViewModel()
{
Videos = new ObservableCollection<Video>();
FavoriteVideos = new ObservableCollection<Video>();
}
private Video selectedVideo;
public Video SelectedVideo
{
get { return selectedVideo; }
set { selectedVideo = value; NotifyPropertyChanged("SelectedVideo"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In App.xaml.cs you'll define the ViewModel
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
Then you can set the data context of both pages (MainPage and ther other) in the constructor like this:
DataContext = App.ViewModel;
Now you'll only have to bind the items to both lists (StackPanel won't do here. Use other listing like ListBox)
ItemsSource="{Binding Videos}"
ItemsSource="{Biding FavoriteVideos}"
For the navigation if you use ListBox just set the click event
ItemClick="ItemClick"
private void ItemClick(object sender, ItemClickEventArgs e)
{
Video item = e.ClickedItem as Video;
App.ViewModel.SelectedVideo = item;
NavigationService.Navigate(new Uri("/PlayerPage.xaml,UriKind.Relative));
}
At the PlayerPage just bind to SelectedVideo and you're good. For favorite you just have to add the video to the FavoriteList
private void FavoriteButton_Click(object sender, RoutedEventArgs e)
{
if(!App.ViewModel.FavoriteVideos.Contains(App.ViewModel.SelectedVideo)) {
App.ViewModel.FavoriteVideos.Add(App.ViewModel.SelectedVideo);
}
}
Now you can dynamically add data in your app if needed. StackPanel will only help with your static videos and it's not practical to use if the data changes.
Related
I have a NavigationView that loads a page into its associated frame.
On the loaded page there is a button, how can I use this button to change the properties of the NavigationView.
I know that to update the page in the frame is:
Frame.Navigate(typeof(Window2));
So I thought it might be:
Frame.NavigationView.IsEnabled = False
But this isn't valid.
Is there a way to do this?
You can not set NavigationView.IsEnabled directly from loaded page.You can set a property in a viewmodel to bind with the IsEnabled of navigationView.When you navigate a new page by Frame.Navigate(),pass the viewmodel to the new page.You can set the property to false when you click the button in the page.In this case,the navigationView will be disabled.
.xaml:
<NavigationView IsEnabled="{x:Bind MyViewModel.IsEnabled,Mode=OneWay}">
<NavigationView.MenuItems>
<NavigationViewItem Content="Item 1"></NavigationViewItem>
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
.cs:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private bool isEnabled = false;
public bool IsEnabled {
get { return isEnabled; }
set {
isEnabled = value;
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed partial class MainPage: Page
{
public MainPage()
{
this.InitializeComponent();
MyViewModel = new ViewModel();
}
private ViewModel MyViewModel { get; set; }
}
When you want to navigate a new page,pass the viewmodel:
ContentFrame.Navigate(typeof(Page1),MyViewModel);
Page1.cs:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
isEnabledVM = (e.Parameter as ViewModel);
}
private ViewModel isEnabledVM { get; set; }
private async void Button_Click(object sender, RoutedEventArgs e)
{
isEnabledVM.IsEnabled = false;
}
I created next WPF controls : Window1, View, Edit in different files:
In MainWindow.xaml MainWindow contein two ContentControls
<Grid>
<ContentControl x:Name="Viewer" Content="ContentControl1" DockPanel.Dock="Top" Height="Auto" RenderTransformOrigin="0.5,0.5">
</ContentControl>
<ContentControl x:Name="Editor" Content="ContentControl2" DockPanel.Dock="Top" Height="Auto" VerticalAlignment="Bottom" RenderTransformOrigin="0.5,0.5" Visibility="Hidden"/>
</Grid>
This is part of .cs file:
public partial class MainWindow : Window
{
View v = new View();
Edit e = new Edit();
public MainWindow()
{
InitializeComponent();
Window1.Viewer.Content = v;
Window1.Editor.Content = e;
}
public void swichToEditor()
{
Window1.Editor.Visibility = System.Windows.Visibility.Visible;
Window1.Viewer.Visibility = System.Windows.Visibility.Hidden;
}
public void swichToViewer()
{
Window1.Ediort.Visibility = System.Windows.Visibility.Hidden;
Window1.Viewer.Visibility = System.Windows.Visibility.Visible;
}
}
ComtentControl1 with x:name=Editor contain this userControl:
public partial class Edit : UserControl
{
public Edit()
{
this.InitializeComponent();
}
private void Button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
}
}
ComtentControl2 with x:name=Viewer contain this userControl:
public partial class View : UserControl
{
public View()
{
this.InitializeComponent();
}
private void Button2_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
}
}
When button1 will be pressed i need that Editor will be hide and Viewer will be visible. and vice-versa
I know better c++ syntax. and signal-slot method closer to me :)
What your advice to me?
You can use the RoutedEvent's behavior. You just need to register your Window to the Click RoutedEvent. Take a look:
public partial class MainWindow : Window
{
View v = new View();
Edit e = new Edit();
public MainWindow()
{
InitializeComponent();
Window1.Viewer.Content = v;
Window1.Editor.Content = e;
EventManager.RegisterClassHandler(GetType(), Button.ClickEvent, new RoutedEventHandler(OnButtonClick));
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
if (e.Source is View)
{
SwichToEditor();
}
else
{
SwichToViewer();
}
}
public void SwichToEditor()
{
Window1.Editor.Visibility = System.Windows.Visibility.Visible;
Window1.Viewer.Visibility = System.Windows.Visibility.Hidden;
}
public void SwichToViewer()
{
Window1.Ediort.Visibility = System.Windows.Visibility.Hidden;
Window1.Viewer.Visibility = System.Windows.Visibility.Visible;
}
}
Then you do not need to have a button click event handler in your UserControls.
I hope this sample can help you.
I have a databound FlipView on my page that works great. I have 2 Buttons on my page that I want to use to navigate the FlipView forward and back. The buttons work when I use Mouse Mode in the Simulator or actually use my mouse in Local Machine mode.
But when I use 'Basic touch mode' or use my app on an actual touch screen and tap the buttons, the FlipView navigates to next view but then flashes/flips back.
Here is my FlipView:
<FlipView x:Name="Flipper" ItemsSource="{Binding Path=Views}">...</FlipView>
And my Button:
<Button x:Name="ForwardButton" IsEnabled="{Binding CanGoForward}" Style="{StaticResource ForwardButtonStyle}" Tapped="ForwardButton_OnTapped" />
And my event:
private void ForwardButton_OnTapped(object sender, TappedRoutedEventArgs e)
{
if (Flipper.SelectedIndex >= Flipper.Items.Count - 1)
{
Flipper.SelectedIndex = 0;
}
else
{
Flipper.SelectedIndex++;
}
e.Handled = true;
}
Update:
Adding OnSelectionChanged to the FlipView, I'm seeing it hit twice in Basic touch mode, but in Mouse mode only once, again Mouse mode works as expected.
Yeah, so I implemented it like this:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
ViewModel ViewModel { get { return this.DataContext as ViewModel; } }
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.ViewModel.MovePrevious();
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
this.ViewModel.MoveNext();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel() { SelectedThing = ThingList.First(); }
List<int> m_ThingList = Enumerable.Range(1, 50).ToList();
public List<int> ThingList { get { return m_ThingList; } set { SetProperty(ref m_ThingList, value); } }
int m_SelectedThing = default(int);
public int SelectedThing { get { return m_SelectedThing; } set { SetProperty(ref m_SelectedThing, value); } }
internal void MovePrevious()
{
var _CurrentIndex = ThingList.IndexOf(this.SelectedThing);
try { SelectedThing = ThingList[--_CurrentIndex]; }
catch { SelectedThing = ThingList.First(); }
}
internal void MoveNext()
{
var _CurrentIndex = ThingList.IndexOf(this.SelectedThing);
try { SelectedThing = ThingList[++_CurrentIndex]; }
catch { SelectedThing = ThingList.Last(); }
}
public event PropertyChangedEventHandler PropertyChanged;
void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Page.BottomAppBar>
<AppBar>
<Grid>
<Button HorizontalAlignment="Left" Click="Button_Click_1">Previous</Button>
<Button HorizontalAlignment="Right" Click="Button_Click_2">Next</Button>
</Grid>
</AppBar>
</Page.BottomAppBar>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<FlipView x:Name="MyFlip"
ItemsSource="{Binding ThingList}"
SelectedItem="{Binding SelectedThing,
Mode=TwoWay}" />
</Grid>
Well, it worked correctly. Could you double check? Or provide more info? Otherwise, maybe an update to your implementation using mine as a guide can solve this for you. I hope so.
In all reality I would have used a DelegateCommand to handle the buttons. But I didn't want to overdo the example with too much boiler plate code. If you want to see what I would have done, you can look here: http://blog.jerrynixon.com/2012/08/most-people-are-doing-mvvm-all-wrong.html
I have MainWindow containing a datagrid and a "filter panel". The filter panel can change by a user input(button click). I try to achieve it with databinding. The problem that Im facing is the filter panel(which is a user control) is not loaded or refreshed.
Mainwindow xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="253*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="23,28,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="200" ItemsSource="{Binding OverviewableItems}" />
<UserControl Grid.Column="1" Content="{Binding UserControl}" DataContext="{Binding}" Grid.ColumnSpan="2" />
<Button Content="PersonFilter" Height="23" HorizontalAlignment="Left" Margin="23,268,0,0" Name="buttonPersonFilter" VerticalAlignment="Top" Width="75" Click="buttonPersonFilter_Click" />
<Button Content="ProjectFilter" Height="23" HorizontalAlignment="Left" Margin="132,268,0,0" Name="buttonProjectFilter" VerticalAlignment="Top" Width="75" Click="buttonProjectFilter_Click" />
</Grid>
code behind:
private ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(new DataProvider());
DataContext = _viewModel;
_viewModel.PropertyChanged += _viewModel.SetFilterType;
InitializeComponent();
}
private void buttonProjectFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Project;
}
private void buttonPersonFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Person;
}
First user control:
<Grid>
<DatePicker Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="19,18,0,0" Name="datePickerFundingTo" VerticalAlignment="Top" Width="115" Text="{Binding ElementName=ProjectFilter, Path=FundingTo}" />
</Grid>
code behind for this user control is only this:
public DateTime FundingTo { get; set; }
public ProjectFilter()
{
FundingTo = DateTime.Now;
InitializeComponent();
}
Other user control: just simply contains a TextBox and a Button, for the sake of simplicity I didnt add any code behind to it.
ViewModel of the MainWindow:
public class ViewModel : INotifyPropertyChanged
{
private UserControl _userControl;
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl == value)
{
return;
}
OnPropertyChanged("UserControl");
_userControl = value;
}
}
private OverviewType _overviewType = OverviewType.None;
public OverviewType OverviewType
{
get { return _overviewType; }
set
{
if (_overviewType == value)
{
return;
}
OnPropertyChanged("OverviewType");
_overviewType = value;
}
}
private ObservableCollection<IOverviewItem> _overviewableItems;
public ObservableCollection<IOverviewItem> OverviewableItems
{
get { return _overviewableItems; }
set
{
if (_overviewableItems == value)
{
return;
}
_overviewableItems = value;
}
}
private readonly DataProvider _dataProvider;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(DataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void SetFilterType(object sender, EventArgs eventArgs)
{
switch (_overviewType)
{
case OverviewType.Project:
_userControl = new ProjectFilter();
break;
case OverviewType.Person:
_userControl = new PersonFilter();
break;
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
}
plus I have an enum OverviewType with None,Project,Person values.
The property changed event fired properly, but the user control is not refreshed. Could anyone enlight me, where is the flaw in my solution?
And the other question I have, how can I communicate from the usercontrols to the mainwindow viewmodel? Forex: the datagrid should be changed according to its filter.
Any help would be greatly appreciated. Cheers!
There are different problems here.
As Clemens said, you must fire your event after the value is updated. But it's not the main issue here.
Second problem: you are affecting your new usercontrol to the private member, so you're totally bypassing your property.
Replace
_userControl = new ProjectFilter();
by
this.UserControl = new ProjectFilter();
Third problem, which is not directly related to your question but actually is your biggest problem: you have an architecture design issue. You're exposing in your viewmodel a UserControl, which is an anti-pattern. Your viewmodel must not know anything about the view, so it must NOT have any reference to the controls inside the view. Instead of the binding you wrote, you could fire an event from the viewmodel and add an event handler in your view so it's your view that updates the usercontrol.
Try to fire the PropertyChanged after changing a property's backing field:
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl != value)
{
_userControl = value; // first
OnPropertyChanged("UserControl"); // second
}
}
}
Similar for OverviewType.
I have a window with a textbox and a submit button. When pressing the submit button, the data in the textbox should populate into the listbox and be saved.
What's the best way of doing this? I tried a recommendation (using ObservableCollection) from an earlier question I had, but I can't seem to get it work. I have tried implementing it like this:
I created a class:
public class AccountCollection
{
private string accountName;
public string AccountName
{
get { return accountName; }
set { accountName = value; }
}
public AccountCollection(string accountName)
{
AccountName = accountName;
}
}
Assigned the binding in my XAML:
<ListBox ItemsSource="{Binding AccountName, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" SelectionChanged="accountListBox_SelectionChanged" />
...and finally, when a user clicks the submit button from another window that contains the Submit button and textbox:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
BindingExpression expression = okBtn.GetBindingExpression(accountaddTextBox.Text);
expression.UpdateSource();
}
But alas, I'm getting nowhere. I get an error message at the GetBindingExpression section:
Argument 1: cannot convert from 'string' to 'System.Windows.DependencyProperty'
What's obvious to me here is that when I created the class I didn't specify anything about the account name from the textbox, so I don't even know if the class is correct.
I'm basically confused and don't know what to do. Any help would be appreciated...
MODEL
// the model is the basic design of an object containing properties
// and methods of that object. This is an account object.
public class Account : INotifyPropertyChanged
{
private string m_AccountName;
public event PropertyChangedEventHandler PropertyChanged;
public string AccountName
{
get { return m_AccountName;}
set
{
m_AccountName = value;
OnPropertyChanged("AccountName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ListBox XAML
<ListBox Name="MyAccounts" DisplayMemberPath="AccountName" />
CODE BEHIND
// create a collection of accounts, then whenever the button is clicked,
//create a new account object and add to the collection.
public partial class Window1 : Window
{
private ObservableCollection<Account> AccountList = new ObservableCollection<Account>();
public Window1()
{
InitializeComponent();
AccountList.Add(new Account{ AccountName = "My Account" });
this.MyAccounts.ItemsSource = AccountList;
}
private void okBtn_Click(object sender, RoutedEventArgs e)
{
AccountList.Add(new Account{ AccountName = accountaddTextBox.Text});
}
}
edit: added displaymemberpath on listbox xaml
Here is a Demo using MVVM approach
ViewModel
public class AccountListViewModel : INotifyPropertyChanged
{
ICommand AddAccountCommand {get; set;}
public AccountListViewModel()
{
AccountList = new ObservableCollection<string>();
AddAccountCommand= new RelayCommand(AddAccount);
//Fill account List saved data
FillAccountList();
}
public AddAccount(object obj)
{
AccountList.Add(AccountName);
//Call you Model function To Save you lIst to DB or XML or Where you Like
SaveAccountList()
}
public ObservableCollection<string> AccountList
{
get {return accountList} ;
set
{
accountList= value
OnPropertyChanged("AccountList");
}
}
public string AccountName
{
get {return accountName } ;
set
{
accountName = value
OnPropertyChanged("AccountName");
}
}
}
Xaml Binding
<ListBox ItemsSource="{Binding Path=AccountList}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
<TextBox Text={Binding Path=AccountName}></TextBox>
<Button Command={Binding Path=AddAccountCommand}><Button>
Xaml.cs Code
# region Constructor
/// <summary>
/// Default Constructor
/// </summary>
public MainView()
{
InitializeComponent();
this.DataContext = new AccountListViewModel();
}
# endregion
The Implementation of INotifyPropertyChanged and forming porpeties is left upto you
Your ItemsSource for your ListBox is AccountName, which is only a string but not a collection.
You need to create a viewmodel (your datacontext for the view) like this:
public class ViewModel
{
public ViewModel()
{
Accounts = new ObservableCollection<string>();
}
public ObservableCollection<string> Accounts { get; set; }
}
Bind ItemsSource to Accounts property:
<ListBox ItemsSource="{Binding Accounts}" Height="164" HorizontalAlignment="Left" Margin="12" Name="accountListBox" VerticalAlignment="Top" Width="161" />
And then, in your click event handler of the button you can simple add the current value of the textbox to your collection:
private void okBtn_Click(object sender, RoutedEventArgs e)
{
Accounts.Add(accountaddTextBox.Text);
}
But don't forget to set the DataContext of your window to the class ViewModel.