How to bind the Image.Source in MVVM correctly? - c#

I spent some time now trying to work this out but I am still stuck on it. I have a WPF application with a MVVM pattern. In my ViewModel I have three cases where:
X needs Y and Y is available
X needs Y and Y is not available
X doesn't need Y
I am trying to set an image icon on my view based on these conditions (Like a Check Mark, exclamation Mark... whatever).
In ViewModel:
I created my properties. On any GUI change, I set the variables based on the above cases similar to the following:
void MyBigFunctionToSetAllProperties()
{
// other cases
// ..
if (NeedsY && YExists)
{
// properties
StatusIconPath = "#\"/Resources/SessionView/X-With-Green-Check-White.png\"";
ResultIconPath = "#\"/Resources/SessionView/Y-White.png\"";
}
}
In View.Cs: I did literally nothing.
In View.xaml: I bind like this:
<StackPanel>
<Image Source="{Binding StatusIconPath} />
</StackPanel>
I still can't see why it is not working. What is that thing that I am missing? Please and thanks.
It did not work to bind the properties directly with the Xaml as recommended. I tried it this way:
VM: sample property:
public BitmapImage VerificationStatusIcon{ get { return new BitmapImage(new Uri(#VerificationStatusIconPath, UriKind.Relative));}}
View Xaml:
<Image Name="verificationStatusImage" Source="{Binding VerificationStatusIcon}" Margin="5,0" Width="40" Height="40"/>

You have a whole bunch of unnecessary characters in your icon paths:
StatusIconPath = "#\"/Resources/SessionView/X-With-Green-Check-White.png\"";
ResultIconPath = "#\"/Resources/SessionView/Y-White.png\"";
Change them to this:
StatusIconPath = "Resources/SessionView/X-With-Green-Check-White.png";
ResultIconPath = "Resources/SessionView/Y-White.png";

. But no images originally to view and no changes..
Verify that the path to the image is correct. Maybe hard code an image to test the control against it.
One other scenario is that the resources are not being copied over for run-time acquisition. Make sure they are actually available during runtime.
can't see why it is not working
Is the main view's DataContext set to the live VM's instance?
What is that thing that I am missing?
If you are sure that the view's datacontext contains the live VM, then make sure that the property StatusIconPath on the VM reports a property change event.
That is so that the XAML control which is bound to it knows that it changed and correspondingly one needs to make sure that the ViewModel which holds StatusIconPath adheres to INotifyPropertyChanged which will facilitate such an operation in general:
private string _StatusIconPath;
public string StatusIconPath
{
get { return _StatusIconPath; }
set
{
_StatusIconPath = value;
PropertyChanged("StatusIconPath");
}
}
I provide more robust example on my blog entitled:
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

It turned out that I have an extra unneeded characters in my ImagePaths as Kyle stated. And then, I needed to set my Image.Source from within my View.cs. At least, this is how it worked for me:
ViewModel Something like this:
if (Whatever)
{
StatusIconPath = "/Resources/SessionView/X-With-Green-Check-White.png";
ResultIconPath = "/Resources/SessionView/Y-White.png";
}
Then in View.cs and on SelectedItemChanged:
private void Grid_SelectedItemChanged(object sender, DevExpress.Xpf.Grid.SelectedItemChangedEventArgs e)
{
string tempStatus = ((SessionViewModel) DataContext).StatusIconPath;
string tempResult = ((SessionViewModel) DataContext).ResultIconPath;
StatusImage.Source = new BitmapImage(new Uri(#tempStatus, UriKind.Relative));
ResultImage.Source = new BitmapImage(new Uri(#tempResult, UriKind.Relative));
}
and in Xaml: just a fallback value(any original/default image we want). Ex:
<Image Name="ResultImage" Source="/EZ3D;component/Resources/SessionView/Retake-White.png"/>

Related

How to create buttons/shorcuts dynamically in XAML/C#

I want to create an application where the user can enter shortcuts to files. I know how to create buttons in code at compile time but I have no idea how to create dynamic buttons where the name and the click event are going to be dynamic.
How hard would it be to create something like the image below? Is that even possible in C#/WPF/XAML?
What would be the logic?
FYI - I don't need help with saving the buttons objects, for this I will be using JSON.
You should create an ItemsControl to show what you want, this could be an approach:
<ItemsControl
ItemsSource="{Binding YourListOfLinkObject}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding WhateverYouWantToShow}"
Command="{Binding YourCommand} "
CommandParameter="{Binding YourFileName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You should create a new (if it's not already created) class with the name of the file, the content you want to show in the button and your command. And when initializing the view, create a list of "Link" object.
The command will be the same for all of them, just declare it in a generic way to open the file you put in the CommandParameter
Now that I know you are using MVVM I will try to expand my answer focus on that.
You need a class that I will call FileLink. FileLink will have, at least, 3 properties:
public string WhateverYouWantToShow - This will be the content of your button
public ICommand YourCommand - This will have a DelegateCommand<string> that will be the one who "does" things. This command will be the same for every item you create. You just need one because you will use the parameter to execute/open one file or another.
public string YourFileName - This will be the string you need to execute your command method. I guess it will be a path or a file name.
Now that we have this class created, when initializing the third view, the one with the buttons, you will have an ObservableCollectionproperty, what I called YourListOfLinkObject, of FileLinkobjects. There you will have to add as many FileLink objects as you got from the database and they will be displayed.
If you need to change the way they are shown you just need to modify the DataTemplate.
If there's something I failed to explain again or you want me to go further just let me know :)
It is possible and simple. You add controls to your container and add container to main form. Click event is simply defined in code (which actually you know at dev time - probably you should instead use user controls).
Below is some partial code, doing a similar thing in a real world Silverlight application from years ago to give the idea:
...
sp.Children.Add(p);
foreach (var slot in group)
{
var color = colors[(int)slot.State];
var name = String.Format("W{0}", slot.When.ToString("yyyyMMddHHmm"));
Rectangle rect = new Rectangle
{
Name = name,
Width = rectWidth,
Height = rectWidth,
Margin = new Thickness(rectMargin),
Stroke = new SolidColorBrush(slot.State == Availability.Booked ? Colors.White : Colors.Black),
StrokeThickness = 1,
Fill = new SolidColorBrush(color),
RadiusX = 2,
RadiusY = 2,
Cursor = (slot.State == Availability.Booked ? Cursors.Arrow : Cursors.Hand)
};
if (slot.State != Availability.Booked)
{
rect.Effect = new DropShadowEffect(); //myDropShadowEffect,
}
if (slot.State != Availability.Booked)
{
rect.MouseLeftButtonDown += new MouseButtonEventHandler(rect_MouseLeftButtonDown);
ToolTipService.SetToolTip(rect, slot.When.ToString("MMM dd,yyyy dddd hh:mm tt"));
}
sp.Children.Add(rect);
}
b.Child = sp;
contentStackPanel.Children.Add(b);
}

Create WPF Function to Hidden Images Controls

I'm currently developing a WPF C# Application that contains some textbox validations. If field is valid it must show a ok validation image if not valid it must show a wrong validation image, like an image below.
My Problem is how to set visibility = visibility.Hidden for all images if I click on cancelar button or another button. I know set img1.visibility = visibility.Hidden;, img2.visibility = visibility.Hidden;, img3.visibility = visibility.Hidden;... Works but i need to create a function to do it. I believe that I create a List of Images and pass this List of parameter to a function works fine and I can use this function for other validations. So how can I do it?
Please check this article : Data Binding
If you implement data binding then you have just to bind properties:
<Image Source="..." Visibility="{Binding Img1Visibility}"/>
Implement ViewModel class via INotifyPropertyChanged
And then simply work with your Properties in code.
UPD
If you want to simply create function to work with your images then move your img1.visibility = visibility.Hidden;, img2.visibility = visibility.Hidden;, img3.visibility = visibility.Hidden; in separate function inside your MainWindow.xaml.cs file, you don't have to pass it as arguments as you work in one MainWindow class.
So simply:
private void Fun()
{
img1.visibility = visibility.Hidden;
img2.visibility = visibility.Hidden;
img3.visibility = visibility.Hidden;
}
And request your Fun() method from ClickButton handler.
Create an array of the image controls and iterate over it.
List<Image> _images = new List<Image>
{
img1,
img2,
...
};
void Cancelar()
{
foreach (var image in _images)
{
image.Visibility = Visibility.Hidden;
}
}
But still, the code is awful. Witness me SO.

Data Binding to member property and providing design time value

I try to understand data bindings in WPF and I could already bring some tests to work. But I'm stuck tight now. :(
For better organization I want to split my code into more classes and bind elements of the same Window to different classes (actually to properties of different classes).
I think I have to set the DataContext of my Window to "this" (The window itself) and use the Binding Path to specify the property to use.
DataContext = this;
-
<Label Content="{Binding Path=_printSettings.CopyCount}"/>
So f.e. I want to bind to CopyCount wich is a property returning a string. That property belongs to the instance in the private field _printSettings of the current window. And _printSetting implements the INotifyPropertyChanged and notifies in a twoway principle.
But the label is empty during design and runtime...
I also noticed that no default values are set in the designer in my previous tests. Does anyone know an implementation? If possible without the use of a fallback value.
-MainWindow.xaml.cs
<Label Content="{Binding ElementName=MainWindow,Path=PrintSettings.CopyCount, FallbackValue=[0]}">
-MainWindow.cs
private PrintSettings _printSettings = new PrintSettings();
public PrintSettings PrintSettings {
get {
return _printSettings;
}
}
public MainWindow()
{
DataContext = this;
}
PrintSettings.cs
private int _copyCount = 1;
//Copy count
public string CopyCount
{
get {
return "" + _copyCount;
}
}
-
EDIT:
added more code again
Binding works against/through public properties. _printSettings is not a property. The Visual Studio "Output" window can show any binding errors you have.
_printSettings and CopyCount should be public.
If it doesnt help, then in xaml Set window Name and binding will look like this
<Label Content="{Binding ElementName=YourWindowName,Path=_printSettings.CopyCount}"/>
or
<Label Content="{Binding ElementName=YourWindowName,Path=DataContext._printSettings.CopyCount}"/>

Add userControl to listbox

I am working on a little project for a contest in my city..and i just hit a brick wall.The thing is: i am creating a userControl in Blend(let's say a canvas,in wich i have a reactangle..a textblock and an image).My problem is that i can not add this to the listboxitem in WPF by code.Addin the userControl one by one in the designer seems to work..but the software is going to work with a variable number of items for the listbox.
private void mainPane1_Loaded(object sender, RoutedEventArgs e)
{
MessageBox.Show("layout updated");
questions cq;
Button btn;
for (int i = 1; i <= 10; i++)
{
btn = new Button();
btn.Content = "intreb" + i.ToString();
cq = new questions();
Canvas.SetZIndex(cq, 17);
//cq.questionHolder.Text = "intrebare" + i.ToString();
listaintrebari.Items.Add(cq);
MessageBox.Show("intrebare" + i.ToString());
listaintrebari.Items.Add(btn);
//MessageBox.Show("layout updated");
}
}
questions is my UserControl and listaintrebari is the listbox.I tried to add some buttons and it works great...but it seems to hate my userControl.
I am waiting for your thoughts on how to resolve this issue, and if you have any sugestions on what other is best to use in my situation and how..it would be great.Thank you!
Ok, here's some actual code that might help you out.
I will be using several WPF concepts that you might want to study further : DataBinding, DataTemplates, ImageSources, ObservableCollections
First you need to create (if you don't have it yet) an underlying class for your Questions. The simplest you can get would be something like this :
internal class Question
{
public ImageSource QuestionImage { get; set; }
public string QuestionText { get; set; }
}
Then in your screen's code behind (yes, we are not at MVVM yet), you should create an ObservableCollection of Question and pouplate them with your questions
I have smth like this:
public ObservableCollection<Question> Questions { get; private set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Questions = new ObservableCollection<Question>();
for (int i = 1; i <= 10; i++)
{
var newQ = new Question { QuestionText = "intrebarea " + i.ToString(), QuestionImage = _get your image as a ImageSource here_ };
Questions.Add(newQ);
}
}
The this.DataContext = this is very important, otherwise your Data Binding will not work.
In your design area, create a list and bind it to the Questions collection you created. The way the question is displayed in the list is driven by the "ItemTemlpate" as below.
<ListBox ItemsSource="{Binding Questions}">
<ListBox.ItemTemplate>
<StackPanel>
<Image Source="{Binding QuestionImage}" Height="20" Width="20"/>
<TextBlock Margin="5" Text="{Binding QuestionText}" />
</StackPanel>
</ListBox.ItemTemplate>
</ListBox>
You can replace the I have there with your UserControl contents or event the UserControl itself, but make sure to preserve the Bindings to the objects in your Question class.
Like I said above, many things might not make sense at this point so make sure you read about them : What is a data Binding, What is a DataContext, What is an ObservableCollection. Also, try looking at MVVM when you get a chance...
Lastly, if you are unsure how to get an ImageSource when you have a jpg or png file in your project:
public ImageSource GetImagesource(string location)
{
var res = new BitmapImage()
res.BeginInit();
res.UriSource = new Uri("pack://application:,,,/ApplicationName;component/" + location);
res.EndInit();
return res;
}
The right way to handle this kind of situation is by having a data model with a collection of your questions. Then bind your ListBox.ItemsSource to the collection and provide a DataTemplate that uses your UserControl.
Use the ListBox's ItemTemplate to define what you want each instance of your object to look like, then bind the ListBox's ItemsSource to a collection of that type.
You need to create a collection (e.g. List) of your control and bind the collection to the ListBox.

silverlight Binding from VM collection to View not working need some help

I know I am missing something here and I could use a pointer. Within a project I have an expander control when this control is clicked it makes a RIA call to a POCO within my project to retreive a second set of data. I am using the SimpleMVVM toolkit here so please let me know if I need to expand on any additional areas.
Within the xaml the expander is laid out as
<toolkit:Expander Header="Name" Style="{StaticResource DetailExpanderSytle}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<ei:CallMethodAction
TargetObject="{Binding Source={StaticResource vm}}"
MethodName="showWarrantNameDetail"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<sdk:DataGrid AutoGenerateColumns="true" ItemsSource="{Binding NameResult}" AlternatingRowBackground="Gainsboro" HorizontalAlignment="Stretch" MaxHeight="200">
</sdk:DataGrid>
<local:NameContainer DataContext="{Binding}" />
</StackPanel>
</toolkit:Expander>
I am using the expression Dll coupled with Simple MVVM to get at the methods in the view model vs commands.
Within the view model I have the following code
public void showWarrantNameDetail()
{
//set flags
IsBusy = true;
CanDo = false;
EntityQuery<WarrantNameDataView> query = App.cdContext.GetWarrantNameDataViewsQuery().Where(a => a.PrimaryObjectId == Convert.ToInt32(RecID));
Action<LoadOperation<WarrantNameDataView>> completeProcessing = delegate(LoadOperation<WarrantNameDataView> loadOp)
{
if (!loadOp.HasError)
{
processWarrantNames(loadOp.Entities);
}
else
{
Exception error = loadOp.Error;
}
};
LoadOperation<WarrantNameDataView> loadOperation = App.cdContext.Load(query, completeProcessing, false);
}
private void processWarrantNames(IEnumerable<WarrantNameDataView> entities)
{
ObservableCollection<WarrantNameDataView> NameResult = new ObservableCollection<WarrantNameDataView>(entities);
//we're done
IsBusy = false;
CanDo = true;
}
When I set a break on the processWarrantName I can see the NameResult is set to X number of returns. However within the view the datagrid does not get populated with anything?
Can anyone help me understand what I need to do with the bindings to get the gridview to populate? Other areas of the form which are bound to other collections show data so I know I have the data context of the view set correctly. I've tried both Data context as well as Items Source and no return?
When I set a break on the code the collection is returned as follows so I can see that data is being returned. Any suggestions on what I am missing I would greatly appreciate it.
With regards to the page datacontext I am setting it in the code behind as follows:
var WarrantDetailViewModel = ((ViewModelLocator)App.Current.Resources["Locator"]).WarrantDetailViewModel;
this.DataContext = WarrantDetailViewModel;
this.Resources.Add("vm", WarrantDetailViewModel);
Thanks in advance for any suggestions.
Make ObservableCollection<WarrantNameDataView> NameResult a public property of your ViewModel class. Your view will not be able to bind to something that has a private method scope (or public method scope, or private member scope).
//declaration
public ObservableCollection<WarrantNameDataView> NameResult { get; set }
//in the ViewModel constructor do this
NameResult = new ObservableCollection<WarrantNameDataView>();
//then replace the original line in your method with:
//EDIT: ObservableCollection has no AddRange. Either loop through
//entities and add them to the collection or see OP's answer.
//NameResult.AddRange(entities);
If processWarrantNames gets called more than once, you might need to call NameResult.Clear() before calling AddRange() adding to the collection.
Phil was correct in setting the property to public. One note I'll add is there is no AddRange property in SL or ObservableCollection class that I could find. I was able to assign the entities to the OC using the following code
private ObservableCollection<WarrantNameDataView> warrantNameResult;
public ObservableCollection<WarrantNameDataView> WarrantNameResult
{
get { return warrantNameResult; }
set
{
warrantNameResult = value;
NotifyPropertyChanged(vm => vm.WarrantNameResult);
}
}
and then within the return method
WarrantNameResult = new ObservableCollection<WarrantNameDataView>(entities);
This worked and passed to the UI the collection of data.

Categories

Resources