Inquiridor
Conceito de threads

Pergunta
-
Pessoal, tenho um programa que precisa ler umas imagens de um pen-drive e antes de exibí-las, faz um redimensionamento para não ficar tão pesado. O código funciona, mas tem o incoveniente de atualizar o form somente depois que todo redimensionamento acontece. Em outras palavras, parece que o from congelou. Como soluciono para que a cada imagem seja carregada assim que é redimensionada?
Vi que o caminho é fazer threads para acelerar esta etapa. Colarei os códigos das duas funções abaixo e agradeço quem puder me orientar.
Abraços!
string DiretorioFotos = "";
public void FazMiniaturas()
{
string extensao = "*.jpg";
string[] DiretoriosSplited = DiretorioFotos.Split(new char[] { '|' });
// foreach dos diretorios
foreach (string DiretorioPhotos in DiretoriosSplited)
{
if (DiretorioPhotos != "")
{
//foreach dos arquivos
foreach (string arquivos in System.IO.Directory.GetFiles(DiretorioPhotos, extensao))
{
//Retira o caminho qualificado e obtem somente o nome do arquivo
string nomeArquivo = System.IO.Path.GetFileName(arquivos);
var OrigemFoto = Image.FromFile(arquivos);
var LarguraReferencia = (double)OrigemFoto.Width / (double)Screen.PrimaryScreen.Bounds.Width * 5;
var AlturaReferencia = (double)OrigemFoto.Height / (double)Screen.PrimaryScreen.Bounds.Height * 5;
int MiniLargura, MiniAltura;
if (LarguraReferencia < AlturaReferencia)
{
MiniLargura = (int)((double)OrigemFoto.Width / LarguraReferencia);
MiniAltura = (int)((double)OrigemFoto.Height / LarguraReferencia);
}
else
{
MiniLargura = (int)((double)OrigemFoto.Width / AlturaReferencia);
MiniAltura = (int)((double)OrigemFoto.Height / AlturaReferencia);
}
var Miniatura = new Bitmap(MiniLargura, MiniAltura);
Graphics g = Graphics.FromImage(Miniatura);
g.DrawImage(OrigemFoto, new Rectangle(0, 0, MiniLargura, MiniAltura));
Miniatura.Save(@"C:\miniaturas\" + nomeArquivo, System.Drawing.Imaging.ImageFormat.Jpeg);
g.Dispose();
}
}
}
private void CarregaFotos()
{
int x = 0, y = 50, quantidade=1;
string extensao = "*.jpg";
string[] DiretoriosSplited = DiretorioFotos.Split(new char[] { '|' });
// foreach dos diretorios
foreach (string DiretorioPhotos in DiretoriosSplited)
{
if (DiretorioPhotos != "")
{
//foreach dos arquivos
foreach (string arquivos in Directory.GetFiles(DiretorioPhotos, extensao))
{
string nomeArquivo = Path.GetFileName(arquivos);
PictureBox pb = new PictureBox();
panelFotos.Controls.Add(pb);
//Cada pictureBox é associado aos arquivos encontrados no diretorio selecionado
pb.Tag = arquivos;
//Evento do click para selecionar/deselecionar foto
pb.Click += new EventHandler(pb_Click);
pb.Size = new Size(300, 400);
pb.SizeMode = PictureBoxSizeMode.Zoom;
pb.BorderStyle = BorderStyle.Fixed3D;
pb.Image = Image.FromFile(@"C:\miniaturas\" + nomeArquivo);
pb.Location = new Point(x, y);
x += 300;
if (quantidade % 4 == 0)
{
x = 0;
y += 410;
}
lblQuantidade.Text = quantidade.ToString() + " Fotos"; //quantidade.ToString() + " Fotos";
quantidade++;
Label lblNomeArq = new Label();
pb.Controls.Add(lblNomeArq);
lblNomeArq.Text = nomeArquivo;
lblNomeArq.Location = new Point(0, 10);
lblNomeArq.ForeColor = Color.Red;
lblNomeArq.Font = new Font("Microsoft Sans Serif", 12);
lblNomeArq.Size = new Size(pb.Width, 20);
}
}
}
}- Editado MuriloPiracicabano segunda-feira, 20 de maio de 2013 18:20
Todas as Respostas
-
Bom dia..
Se vale de dica, talvez você pudesse usar o Async na chamado do seu método do form, assim você não ficaria preso até terminar. Mas isso só vale com o VS2012, se não for o caso ai teria que usar thread mesmo.
Colei um exemplo abaixo, é bem simples.
public Form1()
{ InitializeComponent();
button1.Click += async (sender, e) => { await Teste(); };
}
async Task Teste()
{
await Task.Delay(10000);
MessageBox.Show("Isso e um teste!!!");
}- Editado Márcio S Silva quarta-feira, 22 de maio de 2013 13:40
-
Márcio S Silva, obrigado pela ajuda.
Estou usando o Visual Studio 2008 a versão do .NET é 3.5. Pode dizer como faço a thread que sugere para este caso? Em todas as vezes que tentei apareceu como resposta:
"Operação entre threads inválida: controle 'panel Fotos' acessado de um thread que não aquele no qual foi criado."
Eu não sei como consertar este problema. Alguém pode ajudar?
-
Murilo, Talvez colocar a parte que redimensiona em uma thread para não prender:
Thread th = new Thread(new ThreadStart(FazMiniaturas));
th.Start();
E colocar no CarregaFotos um File.Exists para não dar kabum na hora que carregar, ou talvez fosse uma boa, você criar uma thread que chama o faz miniatura, e unir o "//foreach dos arquivos" para evitar o kabum. []'s
- Editado Márcio S Silva sexta-feira, 24 de maio de 2013 19:07
- Sugerido como Resposta Márcio S Silva terça-feira, 4 de junho de 2013 02:11
-
Márcio, desculpe a demora. Surgiram outras prioridades no trabalho e agora estou retomando.
O problema persiste na hora de fazer as threads: diz: "Operação entre threads inválida: controle 'panel Fotos' acessado de um thread que não aquele no qual foi criado."
Li sobre o BackgroundWorker. É esse o caminho? E como uso Delegates?
Obrigado.
-
Murilo,
Sim.. é bem por aí.. Acessar controles por threads não dá para ser direto..
Coloquei um exemplo abaixo, veja se te ajuda..
delegate void SetControlValueCallback(Control oControl, string atributo, object valor); private void SetControlPropertyValue(Control oControl, string atributo, object valor) { if (oControl.InvokeRequired) { SetControlValueCallback d = new SetControlValueCallback(SetControlPropertyValue); oControl.Invoke(d, new object[] { oControl, atributo, valor }); } else { Type t = oControl.GetType(); PropertyInfo[] props = t.GetProperties(); foreach (PropertyInfo p in props) { if (p.Name.ToUpper() == atributo.ToUpper()) { p.SetValue(oControl, valor, null); } } } }
Aí você modifica o componente pelo thread usando o método: Por exemplo, um TextBox, mudando o texto:
SetControlPropertyValue(txtNome, "Text", "Marcio");
Veja se te ajuda.. Qualquer coisa grita aí.
Abraços.
Marcio S Silva | http://marsosi.blogspot.com | marsosil@ovi.com
- Sugerido como Resposta Márcio S Silva sexta-feira, 28 de junho de 2013 20:02
-
Marcio,
continuo com problemas. Não entendi como proceder de acordo com o seu código. Eu preciso animar um Progressbar enquanto as fotos são redimensionadas. Então, o que preciso fazer?
O argumento Control oControl do seu código devo substituir por
AnimaBarra (Control progressBar1) ?
Ao mesmo tempo preciso que o Label informe o número da foto que está sendo processado.
Em todos os casos tenho como resposta que o controle foi acessado por outra Thread que não foi a que criou.
Abraços!
-
Murilo,
Coloquei um exemplo abaixo e na galeria utilizando uma parte do trecho do seu fazMiniatura, e trabalhando com threads para a atualização do progressBar e o ListView, usando um ImageList.
Acho que pode ser mais ou menos que estava procurando.
Veja se te ajuda..
http://code.msdn.microsoft.com/Threads-com-ListView-e-7231ab29
Boa sorte, qualquer coisa estamos aí.
Abraços..
using System; using System.Drawing; using System.Linq; using System.Windows.Forms; using System.Reflection; using System.Threading; using System.IO; namespace ExemploDoProgressBar { public partial class Form1 : Form { public Form1() { InitializeComponent(); btnCaminho.Click += new EventHandler(btnCaminho_Click); } void btnCaminho_Click(object sender, EventArgs e) { if (folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtCaminho.Text = folderBrowserDialog1.SelectedPath; Thread th = new Thread(new ThreadStart( populaListView )); th.Start(); } } void populaListView() { FazMiniaturas(); atualizaView(); } public void FazMiniaturas() { string extensao = "*.jpg"; SetControlPropertyValue(progressBar1, "Minimum", 0); string[] arq = Directory.GetFiles(txtCaminho.Text, extensao); SetControlPropertyValue(progressBar1, "Maximum", arq.Count()); SetControlPropertyValue(progressBar1, "Step", 1); int cont = 0; //foreach dos arquivos foreach (string arquivos in arq) { cont++; //Retira o caminho qualificado e obtem somente o nome do arquivo string nomeArquivo = Path.GetFileName(arquivos); var OrigemFoto = Image.FromFile(arquivos); var LarguraReferencia = (double)OrigemFoto.Width / (double)Screen.PrimaryScreen.Bounds.Width * 5; var AlturaReferencia = (double)OrigemFoto.Height / (double)Screen.PrimaryScreen.Bounds.Height * 5; int MiniLargura, MiniAltura; if (LarguraReferencia < AlturaReferencia) { MiniLargura = (int)((double)OrigemFoto.Width / LarguraReferencia); MiniAltura = (int)((double)OrigemFoto.Height / LarguraReferencia); } else { MiniLargura = (int)((double)OrigemFoto.Width / AlturaReferencia); MiniAltura = (int)((double)OrigemFoto.Height / AlturaReferencia); } var Miniatura = new Bitmap(MiniLargura, MiniAltura); Graphics g = Graphics.FromImage(Miniatura); g.DrawImage(OrigemFoto, new Rectangle(0, 0, MiniLargura, MiniAltura)); Miniatura.Save(@"C:\Miniatura\" + nomeArquivo, System.Drawing.Imaging.ImageFormat.Jpeg); g.Dispose(); CallMethod(progressBar1, "PerformStep", null); } } delegate void ControlCallMethod(Control oControl, string metodo, object[] param); private void CallMethod(Control oControl, string metodo, object[] param) { if (oControl.InvokeRequired) { ControlCallMethod d = new ControlCallMethod(CallMethod); oControl.Invoke(d, new object[] { oControl, metodo, null }); } else { Type t = oControl.GetType(); MethodInfo[] method = t.GetMethods(); foreach (MethodInfo p in method) { if (p.Name.ToUpper() == metodo.ToUpper()) { p.Invoke(oControl, param); } } } } delegate void SetControlValueCallback(Control oControl, string atributo, object valor); private void SetControlPropertyValue(Control oControl, string atributo, object valor) { if (oControl.InvokeRequired) { SetControlValueCallback d = new SetControlValueCallback(SetControlPropertyValue); oControl.Invoke(d, new object[] { oControl, atributo, valor }); } else { Type t = oControl.GetType(); PropertyInfo[] props = t.GetProperties(); foreach (PropertyInfo p in props) { if (p.Name.ToUpper() == atributo.ToUpper()) { p.SetValue(oControl, valor, null); } } } } void atualizaView() { int cont = 0; string extensao = "*.jpg"; string[] arq = Directory.GetFiles("c:\\Miniatura", extensao); foreach (string arquivos in arq) { InsertIntoImagem(Image.FromFile(arquivos)); InsertIntoList(arquivos.ToString(), cont); cont++; } } private delegate void InsertIntoListDelegate(string arquivo, int imagem); public void InsertIntoList(string arquivo, int imagem) { if (InvokeRequired) { Invoke(new InsertIntoListDelegate(InsertIntoList), arquivo, imagem); } else { listView1.Items.Add(arquivo, imagem); } } private delegate void InsertIntoImagemDelegate(Image imagem); public void InsertIntoImagem( Image imagem) { if (InvokeRequired) { Invoke(new InsertIntoImagemDelegate(InsertIntoImagem), imagem); } else { imageList1.Images.Add(imagem); } } } }
Marcio S Silva | http://marsosi.blogspot.com | marsosil@ovi.com
- Sugerido como Resposta Márcio S Silva quarta-feira, 3 de julho de 2013 03:21
- Editado Márcio S Silva quarta-feira, 3 de julho de 2013 03:32 Faltou endereço da galeria.
-
Boa tarde, Marcio!
Cara, esse tal de thread é a coisa mais difícil que já vi. Não funciona, não dá certo.
Tentei usar em conjunto o método que redimensiona as fotos e outro que carrega as fotos usando this.refresh(), mas é inútil. Esgota memória e a máquina fica completamente instável. Os erros mais absurdos aparecem.
O caminho é usar threads mesmo, mas mesmo que eu estude, entenda o conceito, na hora de implementar, não funciona.Agradeço imensamente a ajuda, mas nem com o seu exemplo consegui progredir.
Abraços!