我正在搜索大约4个月,以解决我的180个控件(类似于图像框)的图像闪烁问题,这些控件已插入流式布局面板中。 我几乎尝试了所有操作(启用双重闪烁等),但对我没有任何帮助。但是,今天我想出了一个解决方案,那就是将图像大小模式设置为正常模式,而不是缩放或调整图像大小以完全适合类似于图片框的控件。所以我选择了第二个选项,这是我的代码:
public MovieControl(Movies M1, bool show, MainForm current)
{
InitializeComponent();
Current = current;
if (M1.movie_Banner == "") bunifuImageButton1.Image = Properties.Resources.no_image_available;
else
{
WebClient client = new WebClient();
Stream stream = client.OpenRead(M1.movie_Banner);
Bitmap bitmap; bitmap = new Bitmap(stream);
if (bitmap != null)
bunifuImageButton1.Image = new Bitmap((Image)bitmap, new Size(139, 208));
stream.Flush();
stream.Close();
client.Dispose();
}
bunifuImageButton1.Tag = M1;
M1 = null;
}
此外,我想提到的是,线程使用以下几行来调用此代码:
private void RunMoviesThread()
{
ClearMovies();
LoadMoviesThread = new Thread(() => LoadMoviesControls());
LoadMoviesThread.Start();
}
private void LoadMoviesControls()
{
int x = 1;
if (MPageDropDown.InvokeRequired)
this.Invoke((MethodInvoker)delegate { if (MPageDropDown.SelectedItem != null) x = int.Parse(MPageDropDown.SelectedItem.ToString()); });
else if (MPageDropDown.SelectedItem != null) x = int.Parse(MPageDropDown.SelectedItem.ToString());
for (int i = (x - 1) * Max; i < MoviesList.Count() && i < x * Max; i++)
{
MovieControl tmp = new MovieControl(MoviesList[i], ShowMError, this);
if (tmp.InvokeRequired || MoviesFlowPanel.InvokeRequired)
{
MovieControl m1 = null;
try
{
m1= new MovieControl(MoviesList[i], ShowMError, this);
}
catch { }
this.Invoke((MethodInvoker)delegate { MoviesFlowPanel.Controls.Add(m1); });
}
else
MoviesFlowPanel.Controls.Add(tmp);
tmp = null;
}
}
效果很好,而不是花费很长时间来处理。例如;在极少数情况下,图像可能需要大约半秒,最多20秒!你能帮我找到一种更有效的方法吗? 请注意:我的图像以前是通过互联网上传的,而此代码是用C#编写的。此外,无需调整大小,加载过程仅需几秒钟。 **最大值设置为180 预先感谢。
答案 0 :(得分:0)
我不知道这对您有多大帮助,但是值得一试。我并不完全相信WebClient
可以像这样并行化,但是在Pro .NET Performance: Optimize Your C# Applications
的第187页上出现了一个类似的示例,因此我将大步走说可以使用WebClient
来自多个线程。我添加了一个新方法,尽管可以进行检查以确保不需要调用它,但您只能从UI调用它。此方法读取页码,然后启动后台进程,该进程将下载图像并调整其大小,然后在完成所有操作后,将在UI线程中创建控件。
private void GetPageAndLoadControls()
{
if (MPageDropDown.InvokeRequired)
{
MPageDropDown.Invoke((MethodInvoker)(() => GetPageAndLoadControls();));
return;
}
var page = MPageDropDown.SelectedItem != null ?
int.Parse(MPageDropDown.SelectedItem.ToString()) - 1 : 0;
Task.Run(() => LoadMoviesControls(page));
}
private void LoadMoviesControls(int page)
{
var dict = new ConcurrentDictionary<string, Bitmap>();
var movies = MovieList.Skip(page * Max).Take(Max).ToList();
using (var client = new WebClient())
{
Parallel.ForEach(movies, (m) =>
{
Stream stream = null;
Bitmap bmp = null;
try
{
if (!string.IsNullOrWhitespace(m.movie_Banner)
{
stream = client.OpenRead(s);
bmp = new Bitmap(stream);
// Note: I am guessing on a key here, that maybe there is a title
// use whatever key is going to be best for your class
dict.TryAdd(m.movie_Title, new Bitmap(bmp, 139, 208));
}
else dict.TryAdd(m.movie_Title, Properties.Resources.no_image_available);
}
finally
{
bmp?.Dispose();
stream?.Dispose();
}
});
}
// Here we have to invoke because the controls have to be created on
// the UI thread. All of the other work has already been done in background
// threads using the thread pool.
MoviesFlowPanel.Invoke((MethodInvoker)() =>
{
foreach(var movie in movies)
{
Bitmap image = null;
dict.TryGetValue(movie.movie_Title, out image);
MoviesFlowPanel.Controls.Add(
new MovieControl(movie, image, ShowMError, this);
}
});
}
// Changed the constructor to now take an image as well, so you can pass in
// the already resized image
public MovieControl(Movies M1, Bitmap image, bool show, MainForm current)
{
InitializeComponent();
Current = current;
bunifuImageButton1.Image = image ?? Properties.Resources.no_image_available; // Sanity check
bunifuImageButton1.Tag = M1;
}
修改
在编写此代码并对其进行了更多思考之后,我发生了一些事情,您没有发布ClearMovies()
的代码,但是从发布的代码中,我假设您正在处置之前的180个控件并创建180个新控件。最好是使用上面代码中显示的相同方法,然后代替“创建”新控件,而只是更新现有控件。您可以向用户控件添加一个更新方法,该方法仅更改图像和存储在控件中的Movie项目。这样可以节省每次创建新控件的开销,从而可以提高性能。您可能需要在Invalidate
方法的末尾调用Update
。不确定,由于我无法真正测试所有内容,因此只想提一下。另外,我不会使用Tag
属性来保存Movie
对象,而是将一个成员变量添加到您的UserControl中。这将增加类型安全性,并使其更清楚地保存在何处。
public class MovieControl : Control
{
public Movies Movie { get; protected set; }
// Changed the constructor to now take an image as well, so you can
// pass in the already resized image
public MovieControl(Movies M1, Bitmap image, bool show, MainForm current)
{
InitializeComponent();
Current = current;
bunifuImageButton1.Image = image ?? Properties.Resources.no_image_available; // Sanity check
Movie = M1;
}
public void UpdateMovieAndImage(Movies movie, Image image)
{
// Only dispose if it isn't null and isn't the "No Image" image
if (bunifuImageButton1.Image != null
&& bunifuImageButton1.Image != Properties.Resources.no_image_available)
{
binifuImageButton1.Image.Dispose();
}
bunifuImageButton1.Image = image;
Movie = movie;
}
}