WPF different results, same code from different file - c#

What I'm trying to do is update a GridView on a page. In my page behind code I have a method that returns a DataTable from a query from a database. I then want to set the DataContext of the DataGrid to the returned DataTable. If the GridView is on the main window of the application I can run the code and set the DataContext, but if I try to do the same from even the behind code of the page the GridView doesn't change. I've set breakpoints and I can verify that the DataTable does contain valid data. Why will the GridView work in the main window, but not in the page?
I'm not sure what code to include, but here's what I can think of to include.
Code that returns the DataTable is in the items.xaml.cs file.
public partial class EAHQ_Items : Page
{
public static DataTable ReadAll(string keyword)
{
CRUD.sql = "SELECT id, name " +
"FROM eahqItems " +
"WHERE name LIKE #keyword1 OR #keyword2 ORDER BY id ASC";
string strKeyword = string.Format("%{0}%", keyword);
CRUD.cmd = new MySqlCommand(CRUD.sql, CRUD.con);
CRUD.cmd.Parameters.Clear();
CRUD.cmd.Parameters.AddWithValue("keyword1", strKeyword);
CRUD.cmd.Parameters.AddWithValue("keyword2", keyword);
DataTable dt = CRUD.PerformCRUD(CRUD.cmd);
return dt;
}
}
I have an items.xaml file with a corresponding items.xaml.cs file. In the main window there is a frame that displays the items.xaml file. The items.xaml file contains the following controls.
<Grid>
<DataGrid x:Name="EAHQ_DG_Items" Margin="0,50,0,0" ItemsSource="{Binding}"/>
<Label Content="EAHQ Items" HorizontalAlignment="Center" Margin="0,20,0,0" VerticalAlignment="Top"/>
</Grid>
The main windows have the following controls.
<DataGrid x:Name="dgItems" Grid.Column="2" Margin="10,120,10,0" Grid.Row="1" VerticalAlignment="Top" Height="100" ItemsSource="{Binding}"/>
The main windows behind code has the following line when the application loads. It's loading the DataContext of the control in the main window.
dtItems.DataContext = items.ReadAll("");
This code is what is in the behind code of the page.
public static void LoadData(string keyword)
{
EAHQ_Items p = new EAHQ_Items();
p.EAHQ_DG_Items.DataContext = ReadAll("");
}
As you can see, both lines run the same code, but the page DataGrid doesn't update. Sorry for the long post, but it's the best way I could think to ask.
Thank you!

As you can see, both lines run the same code
No, they certainly don't. You are creating a new instance of EAHQ_Items in LoadData and then set the DataContext of this one.
Your setup is a bit unclear but you should just be able to set the DataContext of the control in the code-behind of the page, just like you do in the window:
public partial class Items : Page
{
public Items()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
EAHQ_DG_Items.DataContext = ReadAll("");
}
}

Related

Async Loading UserControls in ItemControl WPF C#

I would like to print the movie information from the tmdb api and print the movie information into the usercontrols. I want the program to show a poster, its name, its release date, how many votes and so on. My problem starts right here, I want to load usercontrolls as async, so I want all of them loaded in my main window at the same time. But I can't do it, they're loading one by one. Is there a way to use Async in the UI update? Or is there another way to achieve this? I want to get the names of all 20 movie posters at the same time as the webpages and get these 20 usercontrol added to my main window at the same time. I am using this code right now:
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
await GetPopularMoviesAsync();
}
public async Task GetPopularMoviesAsync()
{
SearchContainer<SearchMovie> popularMovies = await client.GetMoviePopularListAsync("en", 1);
List<SearchMovie> popularMovieList = popularMovies.Results;
foreach (var searchMovie in popularMovieList)
{
MovieUSC mov = new MovieUSC();
var image = await GetMovieImage(searchMovie);
GetPosterFromFile(image, mov.MoviePoster);
mov.Name = "PopularMovies" + searchMovie.Id;
mov.MovieName.Text = searchMovie.OriginalTitle;
mov.MovieReleaseDate.Text = "(" + searchMovie.ReleaseDate.Value.Year + ")";
mov.MovieRatingBar.Value = Convert.ToInt32(searchMovie.VoteAverage) / 2;
mov.ClickedMovie += ClickedMovie;
MoviePanel.Items.Add(mov);
}
}
Even if you don't want to go the whole MVVM route, it's still good practice to separate data access from the UI.
Use an ObservableCollection<> to hold the search results, which will be the driver of the display - this will automatically update any bound controls each time it is refreshed.
public ObservableCollection<SearchMovie> PopularMovies { get; }
= new public ObservableCollection<SearchMovie>();
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
var popularMovies = await client.GetMoviePopularListAsync("en", 1);
PopularMovies.Clear();
foreach(var movie in popularMovies)
PopularMovies.Add(movie);
}
Your window should use either a ListBox if you want the user to be able to select a specific movie item, otherwise an ItemsControl. In either case, set ItemsSource to the PopularMovies collection. Use a DataTemplate to define the layout for each movie item - each control is bound to the appropriate property of the SearchMovie object.
<ItemsContol x:Name="MoviesDisplay">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding OriginalTitle}" />
<TextBlock Text="{Binding ReleaseDate.Value.Year}" />
// Add more controls here as required
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Xamarin dxGrid not updating content

I'm using dxGrid in a xamarin application to display contents. This works well so far, but I can't get the grid to update values.
I'm using this grid:
<dxGrid:GridControl
x:Name="grid"
ItemsSource="{Binding articles, Mode=TwoWay}"
SelectedDataObject="{Binding selectedRowObject}"
AutoFilterPanelVisibility="true"
IsReadOnly="true"
SortMode="Multiple">
<dxGrid:GridControl.Columns>
<dxGrid:TextColumn
FieldName="description"
Caption = "Description"
AutoFilterCondition="Contains"/>
<dxGrid:TextColumn
FieldName="id"
Caption = "ID"
AutoFilterCondition="Contains"/>
<dxGrid:TextColumn
FieldName="formattedPrice"
Caption = "Price"
AutoFilterCondition="Contains"/>
</dxGrid:GridControl.Columns>
</dxGrid:GridControl>
In my ViewModel, I use this for the databinding:
public List<Article> articles
{
get
{
return dataItem.articles;
}
}
The dataItem object contains a list of articles besides some other general information. When I open the page, the contents show up correctly, but when I'd like to be able to do this manually. For example, this does not work when I'm returning to the oage via Navigation.PopAsync();.
To reload the list, I use PropertyChanged(this, new PropertyChangedEventArgs(nameof(articles)));, the pendant of this works for all other elements but the grid. The items of the articles-object are correctly set though when I check them in the debugger, they are just not updating.
I found a quick fix that works for me (altough it looks kind of unclean):
In the code-behind of the view:
protected override void OnAppearing()
{
grid.RefreshData();
base.OnAppearing();
}

Why are my XAML controls null?

I have a XAML layout similar to this:
<Grid>
<TextBox x:Name="inputTextBox" LostFocus="inputTextBox_LostFocus" TextChanged="inputTextBox_TextChanged" GotFocus="inputTextBox_GotFocus" />
<ComboBox x:Name="inputComboBox" SelectionChanged="inputComboBox_SelectionChanged">
<ComboBoxItem IsSelected="True">10</ComboBoxItem>
<ComboBoxItem>15</ComboBoxItem>
<ComboBoxItem>20</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="inputComboBoxTwo" SelectionChanged="inputComboBoxTwo_SelectionChanged">
<ComboBoxItem IsSelected="True">1</ComboBoxItem>
<ComboBoxItem>2</ComboBoxItem>
<ComboBoxItem>3</ComboBoxItem>
</ComboBox>
</Grid>
Pretty simple. In the codebehind C# file, I use these controls to take in a double from the TextBox, some more ints from the ComboBoxes, then I create a calculator type object with the data from the controls. I make the calculation and display the results in some other TextBlocks.
namespace TipCalc
{
public sealed partial class MainPage : Page
{
Calc x = new Calc();
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
//
//Appropriate event handlers from XAML controls that all call the calculation method.
//
private void calcIt()
{
x.amt = double.Parse(inputTextBox.Text);
x.cal1 = int.Parse(inputComboBox.SelectedItem.ToString());
x.cal2 = int.Parse(inputComboBoxTwo.SelectedItem.ToString());
//Send calculated values to output TextBlocks.
}
}
}
When I run this program, I hit a null pointer exception that is thrown when I attempt to access the text property of the TextBox. It turns out that all of the XAML controls are null. However, _contentLoaded is set to true and the code definition for this.IntializeComponent looks correct behind the scenes.
Why are all my controls set to null when it seems like everything is working correctly? Is there a way to manually initialize them if they aren't correctly being initialized automatically? Am I doing anything wrong?
the code run like:
Calc x = new Calc();
this.InitializeComponent();
Calc() was first than InitializeComponent(), but InitializeComponent() create your controls.
you can change to:
Calc x;
public MainPage()
{
this.InitializeComponent();
x = new Calc();
this.NavigationCacheMode = NavigationCacheMode.Required;
}
I have the same problem with some of my TextBox controls when the class initializes. What I did to solve this is not the real and perfect solution because not all the controls (TextBox, ComboBox, RadioButton, etc) are null when the class is running, and there's something happening in my code or my app or my VS that I'm missing or doing wrong.... But at least is working fine now. I hope is useful to you:
if(TextBox1 == null)
{
//I'm re-initializing the control because is null
TextBox1 = new TextBox();
}
For your code it should be something like this:
if(inputTextBox == null)
{
inputTextBox.Text = new TextBox();
}
x.amt = double.Parse(inputTextBox.Text);
I hope this 'solution' is good enough for you. And for my poor English I apologize if I have mistakes, is not my native language.

WPF - Binding events to class methods of Item in ItemControl

I'm a bit new to WPF/XAML (though I've learnt C#) and would really appreciate any help for my question. I did look around other posts and google for a while but I can't seem to find a satisfactory or detailed answer to get me going on with my project. Please look below for details. Thanks you in advance!
Objective
I have a class called Tile that consists of a few properties and an event handler.
I also have an ItemControl that has a button (as by the DataTemplate), and whose ItemSource is a collection of Tiles.
Now, I want to bind the "Click" event of the Button so as to invoke the Event Handler method defined in the class Tile.
In other words when I click the button of any item in the ItemControl, the method handler of the corresponding Tile instance (from the collection) must be invoked. How would I tackle this problem?
Below is the entire code, simplified to avoid distraction:
XAML
<Window x:Class="SampleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300">
<!-- Make a ItemControl for "Tile"s. -->
<ItemsControl x:Name="TileList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Wire the click event of this Button
to event handler in the Tile class. -->
<Button Content="Show"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
CODE-BEHIND
namespace SampleWPF
{
public partial class MainWindow : Window
{
ObservableCollection<Tile> tiles;
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
TileList.ItemsSource = tiles;
}
}
public class Tile : INotifyPropertyChanged
{
public string Data
{ /* Accessors and PropertyNotifiers */ }
public Tile(string data)
{ /* Initializing and assigning "Data" */ }
// INotifyPropertyChanged implementation...
// { ... }
// This event handler should be bound to the Button's "Click" event
// in the DataTemplate of the Item.
public void ShowButton_Click(object sender, EventArgs e)
{
MessageBox.Show("Viewing item from: " + this.Data);
}
}
}
Hence, if I click the first "Show" button, the output should be "Viewing item from: Item 1" and if I click the second "Show" Button, the output should be "Viewing item from: Item 2".
So what is the recommended/efficient way to do this? Is my code inappropriate for this requirement?
Event handlers are the wrong approach - use Commands and more importantly MVVM.
As I can see that you are new (and probably from a WinForms or ASP.NET background) you should read this blog to understand how your thinking needs to change - this is the most important part to understand before tackling WPF: http://rachel53461.wordpress.com/2012/10/12/switching-from-winforms-to-wpfmvvm/
You should also read Kent Boogart's blog on how MVVM works from base principles: http://kentb.blogspot.co.uk/2009/03/view-models-pocos-versus.html
Let me start with some basics:
Don't assign itemsource in codeBehind - use Binding like this:
<Controll ItemSource="{Binding MyObservableCollection}"/>
There are many ways You can achieve this. I think that using this.Data is not the best solution for this.
For example if Your tail have ID or something You can assign this id to button CommandParameter like below
<Button CommanParameter="{Binding Path=ID}" Click="ShowButton_Click"/>
And then in Your button_click event u can 'catch' this like this:
public void ShowButton_Click(object sender, EventArgs e)
{
int ID = int.Parse(((Button)sender).CommandParameter.ToString());
}
EDIT
To use this binding You need to set DataContext. You can do this in ctor like this:
public MainWindow()
{
InitializeComponent();
// Adding some sample data for testing.
tiles = new ObservableCollection<Tile>();
tiles.Add(new Tile("Item 1"));
tiles.Add(new Tile("Item 2"));
// below You are setting a datacontext of a MainWindow to itself
this.DataContext = this;
}
ANOTHER EDIT
Let's assume Your tail class have property called ID. If You bound this ID to Button.CommandParameter You can later retrieve the tile with linq like this:
public void ShowButton_click(object sender, EventArgs e)
{
int MyId = int.Parse(((Button)sender).CommandParameter.ToString());
Tile TileIWasSearchingFor = (from t in tiles where t.ID == MyId select t).First();
// do something with tile You found
}
Well since my requirement was rather "simple", I've managed a work around, avoiding commands. Thanks to the answer here by MajkeloDev: https://stackoverflow.com/a/27419974/3998255 for guidance.
This is the final event handler:
public void ShowButton_Click(object sender, EventArgs e)
{
Tile requestingTile = (sender as Button).DataContext as Tile;
if(requestingTile != null)
MessageBox.Show("Viewing item from: " + this.Data);
// Or whatever else you want to do with the object...
}
Also, adding the ItemSource as a XAML attribute:
<ItemsControl x:Name="TileList" ItemsSource="{Binding tiles}">
And setting DataContext in constructor of MainWindow:
public MainWindow()
{
this.DataContext = this;
// Whatever else you want to do...
}
Well it works as required.

Opening new a WPF Window

I'm currently trying to have a pop-out button, such that when it's clicked the current grid will appear in a new window populated with the exact same information.
I got the new Window to appear but I'm trying to have the bindings set but unsure how to do that. If I can get some help please. When I execute OpenChildWindow it opens but nothing populates.
Viewmodel:
public ObservableCollection<PaymentInfo> AmortizationCollection {get; set;}
public void OpenChildWindow()
{
new ScheduleView().Show();
}
LoanView.xaml and ScheduleView.xaml
<telerik:RadGridView Grid.Row="3" Grid.ColumnSpan="3" x:Name="AmortGrid"
ScrollViewer.CanContentScroll ="True"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Height="650" AutoGenerateColumns="True" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ShowGroupPanel="False"
ItemsSource="{Binding AmortizationCollection, Mode=TwoWay}">
My attempt at Content Setting
Scheduleview.xaml.cs
public ObservableCollection<PaymentInfo> AmortizationCollection { get; set; }
public ScheduleView()
{
InitializeComponent();
AmortGrid.ItemsSource = Content;
}
Viewmodel:
public void OpenChildWindow()
{
ScheduleView _newScheduleView = new ScheduleView();
_newScheduleView.Content = AmortizationCollection;
_newScheduleView.Show();
}
The window just appears (Collection) no datagrid or anyhting.
When using WPF, we tend to use DataTemplates to define what our data objects look like in the UI, so to recreate some UI control, we just need to pass the data object and ensure that the DataTemplate is accessible from both locations. You show code from your 'view model', but view models shouldn't know anything about the views and certainly don't open new windows like that.
However, incorrect terminology aside, your simplest solution would be to simply pass the collection to the new Window in the constructor:
public void OpenChildWindow()
{
ScheduleView _newScheduleView = new ScheduleView(AmortizationCollection);
_newScheduleView.Show();
}
Then in ScheduleView.xaml.cs:
public ScheduleView(ObservableCollection<PaymentInfo> amortizationCollection)
{
InitializeComponent();
AmortizationCollection = amortizationCollection;
AmortGrid.ItemsSource = AmortizationCollection;
}
As long as you have the same XAML in each place, it should look the same.
If that is a Window then you can try to set some Content to it, this way the new control that you'll pass will be set as the content of the new window. I think this property is missing in your code untill now.
// create instance
ScheduleView wind = new ScheduleView();
// set the content, can be a window or a page or anything
wind.Content = new SomeControl;
// show it.
wind.Show();
http://msdn.microsoft.com/en-us/library/system.windows.window.content(v=vs.95).aspx
A previous thread from Stack Overflow has this, but in a manner that was too general.
Changing content of Window (WPF)

Categories

Resources